MSW 에 대해 알아보자 그리고 jest.mock 으로 발생하는 문제를 Node 환경에서 MSW로 해결해보자

Jeong·2023년 8월 22일
0

테스트

목록 보기
3/4
post-thumbnail

키워드

  • Service worker
  • MSW(Mock Service Worker)
  • polyfill(폴리필)

최종 목표

테스트와 테스트 하는 방법에 대해 알아보자.

학습 목표

MSW 에 대해 알아보자.

import { render, screen } from '@testing-library/react';

import App from './App';

import fixtures from '../fixtures';

jest.mock('./hooks/useFetchProducts');
jest.mock('./hooks/useFetchUsers');
jest.mock('./hooks/useFetchShops');
jest.mock('./hooks/useFetch블라블라');
jest.mock('./hooks/useFetch블라블라');
jest.mock('./hooks/useFetch블라블라');
jest.mock('./hooks/useFetch블라블라');

test('App', () => {
	render(<App />);

	screen.getByText('Apple');
});

그리고 이전에 jest.mock 으로 발생했던 문제를 Node 환경에서 MSW로 해결해보자.

MSW 란? (Mock Service Worker)

MSW 는 브라우저나 Node 환경에서 오프라인 상황을 지원한다.

오프라인 상태에서는 인터넷 연결이 끊겨있어서, 뭔가를 요청했을 때 올바른 값을 얻기 어렵다.

MSW는 이런 상황에서 어떻게 지원을 할까? Service Worker 를 통해 지원한다.

Service Worker 는 웹 애플리케이션과 브라우저 사이에서 작동하는 백그라운드 스크립트이다. 웹 애플리케이션과 브라우저 사이에서 어떤 주소로 뭔가를 요청했을 때 중간에 가로채고 조작하는 Proxy 역할을 한다.

따라서 MSW는 Service Worker 를 이용하여 Mocking (가짜 응답 생성)을 수행할 수 있다. 실제 네트워크 요청을 보내지 않고도 가짜 응답을 생성하여 테스트하거나 개발할 수 있는 것이다.

MSW는 실제 브라우저나 노드 환경에서 수행할 수 있도록 지원한다.

이전 글에서는 jest.fn 을 활용하여 코드 레벨에서 Mocking 을 수행했다. MSW 는 코드 레벨이 아닌 네트워크 레벨에서 Mocking 을 제공한다.

현재 jest 를 사용하기 때문에 Node 환경에서 MSW 를 활용해보자.

MSW 공식 홈페이지에 가보자. MSW는 Express 보다는 불편하게 생겼지만 비슷하다는 걸 알 수 있다.

MSW 패키지 설치하기

npm i -D msw

jest.config.js 수정하기

jest.config.js 파일의 setupFilesAfterEnv 속성에 setupTests.ts 파일을 추가한다.

setupFilesAfterEnv: [
		'@testing-library/jest-dom/extend-expect',
		'<rootDir>/src/setupTests.ts',
	],

setupTests.ts 추가 및 작성하기

src/setupTests.ts

  • beforeEach: 테스트 하나하나 실행할 때마다 먼저 실행한다.
  • beforeAll: Jest가 실행할 때마다 먼저 실행한다.
  • afterEach: 각각 테스트 끝날 때마다 실행한다.
  • afterAll: Jest 테스트 전부 끝날 때가 끝날 때 실행한다.
import server from './mocks/server';

// msw를 이용한 서버를 일단 띄워서 listen 해준다.
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));

// 하나하나 끝날 때마다 => 초기화 해준다.
afterEach(() => server.resetHandlers());

// Jest 테스트가 전부 끝날 때 => close 해준다. 
afterAll(() => server.close());

onUnhandledRequest: 'error' 는 혹시라도 내가 없는 핸드러를 잡았을 때 에러를 발생시킨다. 실수로 빼먹은 핸들러를 파악할 수 있다.

server.ts 파일 추가 및 작성하기

src/mocks/server.ts

import { setupServer } from 'msw/node';

import handlers from './handlers';

const server = setupServer(...handlers);

export default server;

MSW 를 개발용으로 설치했다. 개발용은 테스트 파일에서 사용해야 ESLint 에러가 안 난다. 지금 그냥 ts 파일에서 작업하려고 해서 ESLint 에러가 난다.

ESLint 에러를 없애는 방법이 2가지 있다.

방법1: src/mocks/.eslintrc.js 파일을 만들어서 Lint를 잡아준다.
방법2: 주석으로 Lint를 잡아준다.

handlers.ts 파일 추가 및 작성하기

src/mocks/handlers.ts

작성할 때 어려움을 겪는다면, 위 공식문서를 참고하자.

import { rest } from 'msw';

import fixtures from '../../fixtures';

const BASE_URL = 'http://localhost:3000';

const handlers = [
	rest.get(`${BASE_URL}/products`, (req, res, ctx) => {
		const { products } = fixtures;

		return res(
			ctx.status(200),
			ctx.json({ products }),
			);
		}),
	];

export default handlers;

ctx.status(200) 는 안 써도 되긴 한다. response 는 기본이 200이다.
Express 보다 불편하다고 했던 점이 Express는
return res( ctx.status(200), ctx.json({ products }), );
이렇게 하지 않고return { products }; 만 해도 돼서 간편했기 때문이다.

이런 식으로도 많이 쓴다.
const BASE_URL = process.env.API_BASE_URL || 'http://localhost:3000'

로딩을 기다리지 않아서 테스트가 실패한다

src/App.test.ts

import { render, screen, waitFor } from '@testing-library/react';

import App from './App';

// jest.mock 불필요.

test('App', async () => {
	render(<App />);
           
    screen.getByText('Apple');
});

App.test.ts 파일을 테스트 돌린다면 결과는 실패한다.

이유를 찾아보자.

App 컴포넌트 내부에서 useFetchProduct 를 사용하고 있고, useFetchProduct 내부에는 useEffect 를 사용한다. 따라서 useEffect 가 끝난 다음에 App 컴포넌트의 return 내부가 render 된다.

테스트는 렌더링을 한 번 한 다음에 바로 체크하기 때문에 원하는 값을 못 찾아서 실패하는 것이다.

값을 로딩하는 시간을 기다려줘야 해결이 된다.

단, 너무 오래 걸리면 안된다. Jest가 정한 타임아웃 범위 내에서 로딩이 되야 한다. waitFor 을 이용하면 타임아웃 범위 내에서 waitFor 안에 있는 구문이 될 때까지 확인한다.

src/App.test.ts

import { render, screen, waitFor } from '@testing-library/react';

import App from './App';

// jest.mock 불필요.

test('App', async () => {
	render(<App />);
	
	await waitFor(() => {
		screen.getByText('Apple');
	});
});

waitFor 내부를 보면 Promise 로 되어 있다. await, async 를 추가해야 정상적으로 동작한다.

fetch 문제로 테스트가 실패한다

ReferenceError: fetch is not defined

이전 테스트에서는 가짜를 다음과 같은 형태로 잡았기 때문에 문제가 없었다. const useFetchProducts = jest.fn(() => fixtures.products);

하지만 지금은 MSW를 이용해서 데이터를 가져오는 작업을 하기 때문에 fetch가 필요하다.

최신 Node 에는 fetch 가 들어왔다. 하지만 이전 버전에서는 브라우저에서 fetch는 동작하지만 Node 에서는 동작하지 않는다.

import { render, screen, waitFor } from '@testing-library/react';

import App from './App';

// jest.mock 불필요.

test('App', async () => {
	render(<App />);
	
	await waitFor(() => {
		screen.getByText('Apple');
	});
});

jest.mock으로 코드 레벨에서 하는 Mocking은 엄청 나열해야 한다는 문제점이 있었고, App 컴포넌트가 아닌 그 하위 컴포넌트에서 jest.mock로 Mocking 한다면 App 컴포넌트에도 Mocking을 써줘야 한다는 문제점이 있었다.
그래서 일단 MSW로 Mocking을 수행하게 돼서 jest.mock을 사용하면서 발생하는 문제를 해결했다. handler 에서 모두 관리가 가능하기 때문이다.

fetch 문제 해결 방법, fetch polyfill

fetch는 윈도우에 있는 것이다. Node에는 윈도우가 없다. (물론 최신 Node에서는 fetch가 가능하다.)

그래서 Node에서 fetch가 없을 때 처리해주기 위해 fetch polyfill 를 사용한다. GitHub에서 만들었다.

npm i -D whatwg-fetch

그리고 whatwg-fetch 를 사용하려면 맨 위에 써야 한다.
setupTests.ts 파일 맨 위에 쓰자.

import 'whatwg-fetch';

import server from './mocks/server';

beforeAll(() => {
	server.listen({onUnhandledRequest: 'error'});
});

afterAll(() => {
	server.close();
});

afterEach(() => {
	server.resetHandlers();
});

마찬가지로 whatwg-fetch는 개발용으로 설치했기 때문에 Lint에러가 발생하면 잡아준다.

MSW를 쓰며 주의할 점

우리가 MSW 사용하여 작성했던 부분을 다시 보자.

import { rest } from 'msw';

import fixtures from '../../fixtures';

const BASE_URL = 'http://localhost:3000';

const handlers = [
	rest.get(`${BASE_URL}/products`, (req, res, ctx) => {
		const { products } = fixtures;

		return res(
			ctx.status(200),
			ctx.json({ products }),
			);
		}),
	];

export default handlers;

이것보다 더 높은 수준으로 만들기 위해서 get, post, patch 등을 사용하여 정말 작동하는 것처럼 만들 수 있다. 하지만 그렇게 되면 사실상 백엔드를 만드는게 된다. 그러니 적절한 선을 지켜서 작성을 해야 한다.

예를 들어, API 스펙은 나왔는데 구현이 안된 경우가 있을 수 있다. 백엔드 개발은 스펙이 간단해도 뒤에서 하는 게 많기 때문에 오래 걸릴 수 있기 때문이다.

그때, 나올 때까지 기다리지 않아도 된다. MSW를 이용해 적당하게 구현을 해서 돌아가게 만들 수 있다.

현재 jest를 사용하고 있어서 테스트 환경이 Node.js 기반이지만, MSW는 웹 브라우저도 지원한다. 그럼 MSW는 테스트에서도 돌고 브라우저에서도 돈다. 단순한 수준이면 테스트도 지원하면서 겸사겸사 웹 브라우저도 작동하게 하는 MSW를 쓰면 되는 것이다.

물론 Express로 임시 서버를 만들어도 되지만, 단순한 수준이면 MSW를 쓰면 된다.

결국 적절한 선을 지켜서 내가 본격적으로 백엔드 개발을 하는 수준으로 쓰지 않으면 MSW는 좋은 선택이 된다.

참고

다음에는?

어디까지나 MSW는 진짜가 아닌 가짜이다.

진짜도 테스트를 해줘야 하는데 이를 위해 E2E 테스트가 있다.

다음에는 E2E 테스트의 끝판왕 도구인 Playwright에 대해 알아보자.

profile
성장중입니다 🔥 / 나무위키처럼 끊임없이 글이 수정됩니다!

0개의 댓글