시뮬레이션 서비스 강좌 제2부분

이것은 나의 모의 서비스 인원 강좌 시리즈의 두 번째 부분이다.Part 1에서 MSW를 설치하고 몇 가지 기본 테스트를 작성하는 방법을 배웠습니다.
본고에서 우리는 도시 고체 폐기물을 깊이 있게 연구하고 이해할 것이다.
  • POST 요청을 테스트합니다.
  • 루트 파라미터가 있는 테스트 요청입니다.
  • 더 많은 테스트 모범 사례.
  • 테스트에서 처리 프로그램을 반복적으로 사용합니다.
  • 선택적 아날로그 오류 상태.
  • 그런 다음 클론the repo을 part-2 분기로 전환합니다.
    git clone [email protected]:jacques-blom/taskhero-web.git
    cd taskhero-web
    git checkout part-2
    yarn
    
    모니터링 모드에서 테스트를 실행하려면:
    yarn test src/App.test.tsx --watch
    

    MSW 테스트 POST 요청을 사용하는 방법


    저희가 뭘 테스트하고 있어요.


    다음 테스트에서는 작업에 삽입하는 프로세스가 유효한지 테스트합니다.

    1. 프로세서 추가


    우리 Taskhero 프로그램은 /tasks 에 발표해서 작업을 삽입합니다.src/mocks/handlers.ts에 이 노드의 게시물을 처리할 새 프로세서를 추가합니다.
    // src/mocks/handlers.ts
    
    import {v4} from 'uuid'
    
    // Use rest.post instead of rest.get
    rest.post(getApiUrl('/tasks'), (req, res, ctx) => {
        // Make sure we receive a request body as a string
        if (typeof req.body !== 'string') throw new Error('Missing request body')
    
        // Parse the request body
        const newTask = JSON.parse(req.body)
    
        // Emulate our real API's behaviour by throwing if we don't receive a label
        if (newTask.label.length === 0) {
            return res(ctx.status(400), ctx.json({message: 'Missing label'}))
        }
    
        // Emulate our real API's behaviour by responding with the new full task object
        return res(
            ctx.json({
                id: v4(),
                label: newTask.label,
                completed: false,
            }),
        )
    }),
    
    프로세서에서는 다양한 장면에서 실제 API의 응답 방식을 시뮬레이션하고 있습니다.
  • 만약 우리가 시체를 받지 못했다면 우리는 버릴 것이다.
  • 사용자가 라벨을 제공하지 않으면 던질 것입니다.
  • 작업이 성공적으로 삽입되면 새 작업 대상을 사용하여 응답합니다.
  • 💡 We can make our handlers as realistic as possible by responding the same way our real API would in different scenarios.


    2. 테스트 작성


    이제 임무가 성공적으로 삽입되었는지 테스트해 봅시다.시작하기 전에 로드가 완료될 때까지 기다린 논리를 추출하여 일을 더욱 간단하게 하겠습니다.
    // src/App.test.tsx
    
    const waitForLoading = () => {
        return waitForElementToBeRemoved(() =>
            screen.getByRole("alert", { name: "loading" })
        )
    }
    

    💡 If you want to know why and how I am using getByRole, check out my post: Don't use getByTestId.


    테스트 추가:
    // src/App.test.tsx
    
    it("inserts a new task", async () => {
        render(<App />, { wrapper: GlobalWrapper })
        await waitForLoading()
    
        const insertInput = screen.getByRole("textbox", { name: /insert/i })
    
        // Type a task and press enter
        userEvent.type(insertInput, "New task")
        fireEvent.keyUp(insertInput, { keyCode: 13 })
    
        // Test the loading state
        expect(insertInput).toBeDisabled()
    
        // Test the success state
        await waitFor(() => expect(insertInput).not.toBeDisabled())
        expect(insertInput).toHaveValue("")
    
        // Test whether the task is displaying on the page
        expect(screen.getByTestId(/task-/)).toHaveTextContent("New task")
    })
    
    위의 테스트에서, 우리는 삽입 작업의 전체 절차를 테스트하고 있다.

    테스트 모범 사례: 더 적고 더 긴 테스트 작성


    이것은 내가 최근에 사용하기 시작한 방법의 하나다.모든 단언을 자신의 테스트로 분해하지 말고 주어진 모든 단언을 하나의 테스트로 조합하라.
    즉, 각 단언에 대한 환경을 설정할 필요가 없으므로 다음을 수행합니다.
  • 테스트 중의 코드가 더욱 적다.
  • 그들은 더 빨리 썼다.
  • 그들은 더 빨리 뛴다.
  • 나는 켄트 도드(Kent C.Dodds)의 문장Write fewer, longer tests
    에서 이 생각을 얻었다.
    테스트를 어떻게 분리하는지에 대한 나의 느낌은 주어진 사용자 흐름이나 상태를 위한 테스트를 작성하는 것이다.따라서, 이 흐름에 대해, 우리는 성공적으로 작업에 삽입되었는지, 다른 테스트는 오류 상태가 처리되었는지 테스트하는 데 사용될 것이다.

    3. 테스트 실패 사례


    사용자가 탭이 없는 작업을 삽입하려고 시도할 때 실패 사례를 위한 테스트를 작성할 수 있습니다.여기에는 테스트 API의 다른 오류도 포함됩니다.

    // src/App.test.tsx
    
    it("displays an error message if the API fails", async () => {
        render(<App />, { wrapper: GlobalWrapper })
        await waitForLoading()
    
        const insertInput = screen.getByRole("textbox", { name: /insert/i })
    
        // Just press enter without typing a label
        fireEvent.keyUp(insertInput, { keyCode: 13 })
    
        // Wait for loading to complete
        await waitFor(() => expect(insertInput).not.toBeDisabled())
    
        // Expect an error alert to display
        expect(screen.getByRole("alert").textContent).toMatchInlineSnapshot()
    })
    

    💡 When testing a UI interacting with an API, always test how the UI reacts to all possible API responses, including errors. Even if your API never throws, something else like a network error could occur.


    테스트 모범 사례: 특정 텍스트 컨텐트를 기대하고 스냅샷을 사용하여 도움말


    위의 예에서 표시된 오류가 실제로 API에서 발생한 오류인지 테스트하기 위해 오류를 표시하기를 원합니다.
    만약 우리가 경보가 존재하는지 테스트만 한다면, 우리는 정확한 오류가 발생했는지 알 수 없다.
    생활을 더욱 간단하게 하기 위해서, 우리는 toMatchInlineSnapshot 을 사용하고, 문자열 .toMatchInlineSnapshot() 을 전달하지 않고 먼저 그것을 호출합니다.그런 다음 처음 테스트를 실행하면 Jest가 자동으로 변경.toMatchInlineSnapshot('"Missing label"')합니다.
    그런 다음 메시지가 변경되면 Jest에서 스냅샷을 업데이트할지 여부를 묻습니다.src/mocks/handlers.ts의 오류 메시지를 직접 볼 수 있도록 변경해 보십시오!

    MSW를 사용하여 라우팅 매개변수가 있는 요청을 테스트하는 방법


    저희가 뭘 테스트하고 있어요.


    다음 테스트에서는 작업을 검사하고 API를 호출한 다음 UI에서 검사된 프로세스로 표시하는 것이 유효한지 확인합니다.

    작업이 완료됨으로 표시되면 응용 프로그램은 /task/1 노드에 POST 요청을 보냅니다. 여기서 1는 작업의 ID입니다.

    1. 프로세서 추가


    // src/mocks/handlers.ts
    
    rest.post(getApiUrl('/task/:id'), (req, res, ctx) => {
        // Make sure we receive a request body as a string
        if (typeof req.body !== 'string') throw new Error('Missing request body')
    
        // Parse the request body
        const newTask = JSON.parse(req.body)
    
        // Get the task ID from the route parameter
        const taskId = req.params.id
    
        // Emulate our real API's behavior by responding with the updated task object
        return res(
            ctx.json({
                id: taskId,
                label: 'Example',
                completed: newTask.completed,
            }),
        )
    }),
    

    💡 You can use the :paramname syntax to specify that this endpoint will take in a parameter. Just like Express.js routes, this parameter will be accessible from the req.params object.


    이 테스트에 대해, 우리는 페이지에 임무를 표시해야 한다.이를 위해 src/mocks/handlers.ts에 프로세서를 만듭니다.
    // src/mocks/handlers.ts
    
    export const singleTask = rest.get(getApiUrl("/tasks"), (req, res, ctx) => {
        return res(
            ctx.json([
                {
                    id: v4(),
                    label: "Example",
                    completed: false,
                },
            ])
        )
    })
    
    handlers 그룹에 전달하지 않고 파일에서 내보내고 있음을 알 수 있습니다.이것은 handlers 수조에 덮어쓰기 /tasks 의 기존 시뮬레이션에 전달되기 때문이다.우리는 본래 그것을 테스트에 포함시킬 수 있었지만, 나는 우리가 그것을 다시 사용할 것이라는 것을 안다.여기에 그것을 첨가해서 다시 사용하기 쉽도록 해라.

    💡 You can make handlers easily reusable by exporting them from a handlers file and importing them in your individual tests.


    2. 테스트 작성


    // src/App.test.tsx
    
    // Import our singleTask handler
    import { singleTask } from "./mocks/handlers"
    
    it("toggles the task completed state", async () => {
        // Mock a single task on the page
        server.use(singleTask)
    
        render(<App />, { wrapper: GlobalWrapper })
        await waitForLoading()
    
        // Click the checkbox
        userEvent.click(screen.getByRole("checkbox", { name: /mark as completed/ }))
    
        // Expect it to be disabled while loading
        expect(screen.getByRole("checkbox")).toBeDisabled()
    
        // Wait for the checkbox to be checked
        await waitFor(() => expect(screen.getByRole("checkbox")).toBeChecked())
    
        // Click the now-checked checkbox
        userEvent.click(
            screen.getByRole("checkbox", { name: /mark as uncompleted/ })
        )
    
        // Wait for the checkbox to be unchecked
        await waitFor(() => expect(screen.getByRole("checkbox")).not.toBeChecked())
    })
    

    3. 테스트 실패 사례



    이 실패 사례를 테스트하기 위해서 우리는 /task/:id 처리 프로그램에서 논리를 조건부로 던질 필요가 없고 이 테스트에서 처리 프로그램을 다시 써서 시종일관 던질 수 있도록 한다.
    // src/App.test.tsx
    
    it("handles toggling the completed state failing", async () => {
        // Re-use our singleTask handler to display a single task on the page
        server.use(singleTask)
    
        // Return an error response from the API when we try to call this endpoint
        server.use(
            rest.post(getApiUrl("/task/:id"), (req, res, ctx) =>
                res(ctx.status(500), ctx.json({ message: "Something went wrong" }))
            )
        )
    
        render(<App />, { wrapper: GlobalWrapper })
        await waitForLoading()
    
        // Click the checkbox
        userEvent.click(screen.getByRole("checkbox", { name: /mark as completed/ }))
    
        // Expect the error to display once loading has completed
        await waitFor(() => {
            return expect(
                screen.getByRole("alert").textContent
            ).toMatchInlineSnapshot()
        })
    
        // Make sure the checkbox stays unchecked
        expect(screen.getByRole("checkbox")).not.toBeChecked()
    })
    

    💡 You can mock your API in a given test to always throw, which makes testing failure cases super simple and predictable.


    우리 망했어!저희가 뭘 배웠죠?


    본 문서에서는 다음과 같은 내용을 설명합니다.
  • POST 요청 및 응답 시 애플리케이션에 미치는 영향을 테스트하는 방법
  • 프로세서 경로에 루트 파라미터를 추가하는 방법.
  • 여러 테스트에서 재사용할 수 있도록 단일 프로세서를 내보내는 방법.
  • 왜 더 적고 더 긴 테스트를 작성하는 것이 더 좋은가.
  • expect 일부 텍스트 내용과 스냅샷을 간단하게 만드는가.
  • 항상 오류를 던지는 처리 프로그램을 작성해서 실패 사례를 테스트하는 방법.
  • 한층 더 읽다


    Mock Service Worker 테스트 및 사용에 관심이 있으시다면 자세한 내용을 게시할 계획입니다.Click here 구독하고 제가 새로운 내용을 발표할 때 통지를 받습니다.
    또한 문제가 있으면 언제든지 저희에게 연락 주십시오.
    만약 이 글이 도움이 된다고 생각하고 다른 사람들도 그럴 것이라고 생각한다면 사랑을 전파하고 공유하는 것을 고려해 보세요.

    너는 아마 나의 다른 문장을 좋아할 것이다

  • Don't use getByTestId 🐙
  • Tutorial: Mock Service Worker is the best way to mock your API
  • 좋은 웹페이지 즐겨찾기