그것은 충분히 순수하게 시작되었습니다. "Axios를 사용하도록 이러한 가져오기 호출을 리팩터링하겠습니다." "무엇이 잘못될 수 있지?"라고 생각했습니다. 알고 보니, 특히 제가 세심하게 제작한 모든 가져오기 모형이 갑자기 초콜릿 찻주전자만큼 유용해졌습니다.
Axios에 대한 모든 모의를 다시 작성하는 대신 이 기회를 이용하여 접근 방식을 현대화하기로 결정했습니다. 모의근로자(MSW)를 입력하세요.
이전에 내 테스트는 다음과 같았습니다.
const mockFetch = vi.fn(); global.fetch = mockFetch; describe("API functions", () => { beforeEach(() => { mockFetch.mockReset(); }); test("fetchTrips - should fetch trips successfully", async () => { const mockTrips = [{ id: 1, name: "Trip to Paris" }]; mockFetch.mockResolvedValueOnce({ ok: true, json: async () => mockTrips, }); const trips = await fetchTrips(mockSupabase); expect(trips).toEqual(mockTrips); }); });
효과가 있었지만 그다지 우아하지는 않았습니다. 각 테스트에는 수동 모의 설정이 필요했고, 모의는 취약했고, 실제로 내 API가 실제 세계에서 어떻게 작동하는지 나타내지 못했습니다. 실제 동작보다는 구현 세부 사항을 테스트하고 있었습니다.
모의 서비스 워커(MSW)는 API 모의에 근본적으로 다른 접근 방식을 취합니다. 함수 호출을 모의하는 대신 네트워크 수준에서 실제 네트워크 요청을 가로챕니다. 이는 다음과 같은 몇 가지 이유로 엄청난 규모입니다.
MSW를 사용한 동일한 테스트는 다음과 같습니다.
// Your API handler definition http.get(`${BASE_URL}/trips`, () => { return HttpResponse.json([ { id: "1", location: "Trip 1", days: 5, startDate: "2023-06-01" }, { id: "2", location: "Trip 2", days: 7, startDate: "2023-07-15" }, ]); }); // Your test - notice how much cleaner it is test("fetchTrips - should fetch trips successfully", async () => { const trips = await fetchTrips(); expect(trips).toEqual([ { id: "1", location: "Trip 1", days: 5, startDate: "2023-06-01" }, { id: "2", location: "Trip 2", days: 7, startDate: "2023-07-15" }, ]); });
더 이상 각 테스트마다 수동으로 모의 설정을 할 필요가 없습니다. MSW 핸들러가 모든 것을 처리합니다. 또한 이러한 핸들러는 여러 테스트에서 재사용될 수 있으므로 중복이 줄어들고 테스트의 유지 관리가 더욱 쉬워집니다.
MSW 설정은 놀라울 정도로 간단해서 즉시 의심이 들었습니다. 테스트에서 이보다 쉬운 일은 없습니다...
beforeAll(() => { server.listen({ onUnhandledRequest: "bypass" }); }); afterEach(() => { server.resetHandlers(); cleanup(); }); afterAll(() => { server.close(); });
그런 다음 실제로 내 API처럼 보이는 핸들러를 만듭니다.
export const handlers = [ http.get(`${BASE_URL}/trips`, () => { return HttpResponse.json([ { id: "1", location: "Trip 1", days: 5, startDate: "2023-06-01" }, { id: "2", location: "Trip 2", days: 7, startDate: "2023-07-15" }, ]); }), ];
오류 처리에 대한 첫 번째 시도는... 음, 낙관적이었다고 가정해 보겠습니다.
export const errorHandlers = [ http.get(`${BASE_URL}/trips/999`, () => { return new HttpResponse(null, { status: 404 }); }), ];
문제는? 보다 일반적인 /trips/:id 처리기가 모든 것을 먼저 포착했습니다. 특정 경로 이전에 Express 앱에 포괄 경로가 있는 것과 같았습니다. 초보 실수였습니다.
어려움을 겪고 테스트에 실패한 후에 경로 자체 내에서 오류를 처리하는 것이 더 나은 접근 방식임을 깨달았습니다.
const mockFetch = vi.fn(); global.fetch = mockFetch; describe("API functions", () => { beforeEach(() => { mockFetch.mockReset(); }); test("fetchTrips - should fetch trips successfully", async () => { const mockTrips = [{ id: 1, name: "Trip to Paris" }]; mockFetch.mockResolvedValueOnce({ ok: true, json: async () => mockTrips, }); const trips = await fetchTrips(mockSupabase); expect(trips).toEqual(mockTrips); }); });
이러한 패턴이 나타났습니다. 별도의 오류 핸들러 대신 실제 API처럼 동일한 위치에서 성공 사례와 오류 사례를 모두 처리할 수 있었습니다. 그것은 "아하!" 중 하나였습니다. 테스트를 통해 실제로 더 나은 디자인을 향해 나아가는 순간입니다.
최종 설정은 유지 관리가 더 용이하고 현실적이며 실제로 실제 문제를 파악하는 데 도움이 됩니다. 다음과 같은 시대는 지났습니다.
// Your API handler definition http.get(`${BASE_URL}/trips`, () => { return HttpResponse.json([ { id: "1", location: "Trip 1", days: 5, startDate: "2023-06-01" }, { id: "2", location: "Trip 2", days: 7, startDate: "2023-07-15" }, ]); }); // Your test - notice how much cleaner it is test("fetchTrips - should fetch trips successfully", async () => { const trips = await fetchTrips(); expect(trips).toEqual([ { id: "1", location: "Trip 1", days: 5, startDate: "2023-06-01" }, { id: "2", location: "Trip 2", days: 7, startDate: "2023-07-15" }, ]); });
대신 다음과 같은 적절한 API 모형이 있습니다.
기대되는 내용은 다음과 같습니다.
때때로 최선의 개선은 강요된 변화에서 비롯됩니다. 간단한 Axios 리팩토링으로 시작된 것이 결국 훨씬 더 나은 테스트 아키텍처로 이어졌습니다. 이것이 바로 리팩토링의 핵심이 아닐까요?
이 글은 원래 제 블로그에 게재된 글입니다. 전체 스택 개발, 테스트 및 API 설계에 대한 자세한 내용을 보려면 저를 팔로우하세요.
위 내용은 모의품 가져오기에서 MSW까지: 테스트 여정의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!