XState 시스템, 유형 및 테스트의 가독성을 위해 노력하는 방법
28809 단어 typescripttestingxstate
할 수 있다
할 수 있다
CodeSandbox에서 기계 재생 및 검사 기계적 특징
제안된 제한된 상태기는 사용자 목록의 UI와 엄격하게 결합됩니다.다음과 같은 이점을 제공합니다.
query
와 employment
유형으로 구성)향후 고려 가능한 미포함 기능:
분기 GitHub 저장소
설계 결정
우선, 왜 XState를 선택했는가: 설령 내가 XState 전문가가 아니더라도, 나는 그것이 유한상태기를 관리, 읽기, 기록하는 고급 도구라고 생각하기 때문에'길다'useReducer
코드를 읽거나 React 갈고리를 뛰어넘어 각각useEffect
의 기능을 이해하려고 하기 때문이다.나는 이것이 이 주제의 좋은 도서라고 생각한다.저는 본고의 계발을 받았고 XState 경험을 얻고 싶은 의지의 계발을 받았습니다. 저는 저/저희가 XState가 상세하게 소개한 개발 모델을 좋아하는지 알고 싶습니다.최근에 작업에서 사용하기 시작했습니다. 저는 현재 XState 작업팀을 이끌고 있습니다.
기계.
이것은
입니다.
유한 상태기 코드에 사용되는 디자인 모드는 다음과 같습니다.
기계의 초기 상태는
idle
: 처음에는 목록을 채우기 위해 인원을 가져와야 하고 인원 페이지를 마운트할 때 발생합니다.어쨌든, 기계를 초기의 '빈' 상태에서 캡처 인원으로 이동하는 것은 제어된 사건으로, 반드시 인원 페이지 자체로 보내서, 기계가 응용 프로그램이 기대하지 않을 때 고의로 캡처를 시작하지 않도록 해야 한다.export const machine = createMachine<Context, Events, States>(
{
initial: 'idle',
// ...
}
SET_QUERY
/SET_EMPLOYMENT
전환은 많은 상태에서 중복된다(5회 미만).이것은 나의 선택이다. 나는 독자들이 불필요한 추상적 때문에 코드에서 도약하는 것이 아니라 모든 상태의 위탁 관리 사건을 즉각 이해하기를 바란다.createMachine
파라미터)으로 이동할 수 있다.이것은 테스트에 있어서 매우 중요합니다. machine.withConfig
함수를 통해 아날로그 가져온 서비스가 아닌 (이 예에서 fetchPeople
함수를 덮어쓰는 것을 허용합니다.export const machine = createMachine<Context, Events, States>(
{
/* ... */
},
{
services: {
// <-- NOTE: Services goe here
// ...
},
},
)
내연 지연 피하기: 전점도 탈온스 지연에 적용된다. 만약 미래 기계의 지연에 변화가 발생하면 테스트에 아무런 변화가 없다. 이것은 탈온스 지연을 덮어씌울 것이다.
export const machine = createMachine<Context, Events, States>(
{
/* ... */
},
{
delays: {
// <-- NOTE: Delays goe here
// ...
},
},
)
이것은입니다.유한 상태기의 전체 코드 타입
여기는the full code of the Finite State Machine입니다.
제한된 상태기 유형에 사용되는 디자인 모델은 다음과 같습니다.
모든 상하문은 그 특정한 리메이크만 포함한다. 기계의 상하문은 시종 모든 속성을 가지지만 각종 유형
InitialContext
, DebounceFetchContext
등은 그 특정한 내용만 지정한다.그것들은 단독으로 사용하는 것이 아니라 Context & InitialContext
식의 교차로를 통과한다.export type InitialContext = {
filter: Filter
debounceFilter: undefined
fetching: false
people: []
fetchErrors: []
}
export type DebounceFetchContext = {
debounceFilter: Filter // <-- NOTE: Specify only the properties to override
}
export type States =
| { value: 'idle'; context: Context & InitialContext }
| { value: 'success'; context: Context & SuccessContext }
| { value: 'fetch'; context: Context & FetchContext }
| { value: 'debounceFetch'; context: Context & DebounceFetchContext } // <-- NOTE: the `Context & DebounceFetchContext` type
| { value: 'failure'; context: Context & FailureContext }
이벤트 범주:
types
모듈에서 이벤트 기준START
이벤트)// ---------------------------------------------------------------
// EVENTS
export type Events = InternalEvents | ExternalEvents | UserEvents
테스트
이것은the full type definitions입니다.
유한 상태기 테스트에 사용되는 디자인 모델은 다음과 같습니다.
강가독성: 나는 테스트의 가독성에 매우 관심이 있다. 왜냐하면 코드가 어떻게 작동하는지 이해하는 도구이기 때문이다. 그리고 그것들의 복잡도는 테스트 중인 코드보다 10배 낮아야 하기 때문이다.분명히 이 화제는 주관적이지만, 나는 미래의 개발자들이 편안함을 느낄 수 있도록 최선을 다했다
defaultFetchData
의 machine.test.ts
참조)it('should eventually reach "success"', done => {
const service = interpret(machine).onTransition(state => {
if (state.matches('success')) {
done() // <-- NOTE: asserting/succeeding first
}
})
service.start()
service.send({
type: 'SUCESSS',
people: [
/*...*/
],
}) // <-- NOTE: acting last
})
하지만{ type: 'SUCESSS', people: [/*...*/] })
는 내부 사건으로 누구도 알 필요가 없고 심지어 기계의 테스트도 알 필요가 없다. 왜냐하면 이것은 세부 사항을 실현하는 것이기 때문이다// ARRANGE
const { service, resolveFetchMock } = createMockedMachine()
// ACT
// NOTE: it's an external event, it's fine expliciting it
service.send({ type: 'START' })
// NOTE: exxternal services are managed through dedicated utilities, allow concentrating on the `data`
// the service should return
resolveFetchMock(defaultFetchData)
// ASSERT
expect(service.state).toMatchObject({
value: 'success',
context: { people: defaultFetchData, fetching: false },
})
createMockedMachine utility
: 나는 먼저 모든 테스트를 작성한 다음에 어떤 코드가 모든 테스트에서 흔히 볼 수 있는 코드인지 자세하게 분석했는데 이 코드들은 그들의 가독성을 떨어뜨렸다.createMockedMachine
실용 프로그램은 최소 설정의 기계를 만들지만 제어 위조fetchPeople
서비스의 실용 프로그램을 되돌려줍니다.ARRANGE/ACT/ASSERT
블록테스트 예
첫 번째 방법 예
it('When the fetch fails, should allow retrying with the same filter and clear the errors', async () => {
// ARRANGE
const debounceDelay = 1
const query = 'Ann Henry'
const filteredFetchData = [annHenry]
const {
service,
fetchMock,
rejectFetchMock,
resolveFetchMock,
waitDebouncedFetch,
} = createMockedMachine(debounceDelay)
// 1. START THE MACHINE
service.send({ type: 'START' })
expect(fetchMock).toHaveBeenCalledTimes(1)
// 2. REJECT THE FETCH
rejectFetchMock(error)
expect(service.state).toMatchObject({
value: 'failure',
context: { fetchErrors: [error] },
})
// 3. CHANGE THE QUERY
service.send({ type: 'SET_QUERY', query })
// 4. DEBOUNCED FETCH
await waitDebouncedFetch()
// 5. REJECT THE FETCH
rejectFetchMock(error)
expect(service.state).toMatchObject({ context: { fetchErrors: [error, error] } })
expect(fetchMock).toHaveBeenCalledTimes(2)
// 6. RETRY FETCHING
service.send({ type: 'RETRY' })
// 7. RESOLVE THE FETCH
resolveFetchMock(filteredFetchData)
expect(service.state).toMatchObject({
value: 'success',
context: { fetchErrors: [], people: filteredFetchData },
})
expect(fetchMock).toHaveBeenCalledTimes(3)
// 8. CHECK THE THIRD FETCH' QUERY
expect(fetchMock).toHaveBeenLastCalledWith(
// machine' context
expect.objectContaining({ filter: expect.objectContaining({ query }) }),
// machine' states and invokeMeta, useless for the purpose of the tests
expect.anything(),
expect.anything(),
)
})
두 번째 방법의 예it('When retrying, should retry with the last query', async () => {
// ARRANGE
const query = 'Ann Henry'
const {
service,
fetchMock,
rejectFetchMock,
resolveFetchMock,
waitDebouncedFetch,
} = createMockedMachine(1)
// ACT
service.send({ type: 'START' })
resolveFetchMock(defaultFetchData)
service.send({ type: 'SET_QUERY', query })
await waitDebouncedFetch()
rejectFetchMock(error)
service.send({ type: 'RETRY' })
await waitDebouncedFetch()
// ASSERT
expect(fetchMock).toHaveBeenLastCalledWith(
// machine' context
expect.objectContaining({ filter: expect.objectContaining({ query }) }),
// machine' states and invokeMeta, useless for the purpose of the tests
expect.anything(),
expect.anything(),
)
})
세 번째 방법의 예it('When the "FAILURE" event occurs, should move from the "fetch" state to the "failure" one', () => {
// ARRANGE
const { machineMock } = createMockedMachine()
// ACT
const actualState = machineMock.transition('fetch', {
type: 'FAILURE',
data: error,
})
// ASSERT
expect(actualState).toMatchObject({
value: 'failure',
context: { fetching: false, fetchErrors: [error] },
})
})
이것은the full code of the tests입니다.테스트 기계 문서의 테스트 서비스 장절 반응에서 기계를 소모하다
이것은the full code of the tests입니다.
포장지
렌더링 트리는
MachineRoot
구성 요소로 포장되어야 하며, 이 구성 요소는 실행 중인 기계 (서비스) 를 React 상하문에 저장해야 한다.React와 통합된 유한 상태기 전체 코드 기계에 접근하다
운행 중인 기계는 React 상하문을 통해 접근할 수 있는데 이것은 소비자가 반드시 알아야 할 실현 세부 사항이다.기계에 연결해야 하는 모든 구성 요소는 내보낸
useMachine
갈고리를 사용해야 합니다. 이것은 React 상하문의 사용법을 완전히 숨깁니다.이것은 미래의 재구성에 특히 유용하다.작업 그룹
저희가 어떻게 하는지에 대한 노트들입니다.
나는 서로 다른 사람이 어떻게 비슷한 문제를 해결하는지 보면 항상 좋다고 말할 수 있다😊
Reference
이 문제에 관하여(XState 시스템, 유형 및 테스트의 가독성을 위해 노력하는 방법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/noriste/how-i-strive-for-xstate-machine-types-and-tests-readability-19f4텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)