
global.fetch를 직접 가로채서 테스트를 진행했습니다.
하지만 이 방식은 매번 코드를 재정의해야 하고, 실제 네트워크 동작을 완벽히 흉내 내기 어렵습니다.npm install -D mswsrc/mocks 폴더를 만들고 handlers.ts 파일을 생성합니다.import { http, HttpResponse } from 'msw';
// 가짜 API 주소를 정의합니다.
// 실제 UserList 컴포넌트가 호출하는 URL과 똑같아야 합니다.
export const handlers = [
http.get('https://jsonplaceholder.typicode.com/users', () => {
// 이 URL로 GET 요청이 오면, 아래 JSON 데이터를 돌려줍니다.
return HttpResponse.json([
{ id: 1, name: 'Leanne Graham (MSW)' },
{ id: 2, name: 'Ervin Howell (MSW)' },
]);
}),
];src/mocks/server.ts 파일을 생성합니다.import { setupServer } from 'msw/node';
import { handlers } from './handlers';
// 위에서 만든 핸들러들을 묶어서 가짜 서버를 생성합니다.
export const server = setupServer(...handlers);src/setupTests.ts 파일을 수정합니다.import '@testing-library/jest-dom';
import { server } from './mocks/server';
import { beforeAll, afterEach, afterAll } from 'vitest';
// [설정] 모든 테스트가 시작되기 전에 가짜 서버를 켭니다.
beforeAll(() => server.listen());
// [정리] 각 테스트가 끝날 때마다 핸들러를 초기화합니다.
// (테스트 중간에 에러 상황으로 바꿨더라도 다시 정상 상태로 되돌립니다)
afterEach(() => server.resetHandlers());
// [종료] 모든 테스트가 끝나면 서버를 끕니다.
afterAll(() => server.close());src/components/UserList.tsx를 수정하여 에러 상태를 처리하도록 만듭니다.import { useEffect, useState } from 'react';
interface User {
id: number;
name: string;
}
export default function UserList() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
// [추가] 에러 메시지를 저장할 상태
const [error, setError] = useState('');
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => {
if (!response.ok) {
throw new Error('서버 에러가 발생했습니다.');
}
return response.json();
})
.then((data) => {
setUsers(data);
setLoading(false);
})
.catch((err) => {
// [추가] 에러 발생 시 상태 업데이트
setError('에러가 발생했습니다.');
setLoading(false);
});
}, []);
if (loading) return <p>불러오는 중...</p>;
// [추가] 에러 메시지가 있으면 표시
if (error) return <p>{error}</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}UserList.test.tsx를 수정합니다.
기존의 global.fetch 코드는 모두 삭제하고, server.use 메서드를 사용하여 특정 테스트에서만 핸들러를 덮어쓸 수 있다import { render, screen } from '@testing-library/react';
import { describe, test, expect } from 'vitest';
import { http, HttpResponse } from 'msw'; // MSW 모듈 import
import { server } from '../mocks/server'; // 가짜 서버 import
import UserList from './UserList';
describe('UserList 컴포넌트 (MSW 적용)', () => {
test('데이터를 불러오는 동안 로딩 문구가 뜨고, 이후 데이터가 표시된다', async () => {
render(<UserList />);
console.log('[Test 1] 로딩 상태 확인');
expect(screen.getByText('불러오는 중...')).toBeInTheDocument();
console.log('[Test 1] MSW 정상 응답 대기');
// handlers.ts에 정의한 "Leanne Graham (MSW)" 데이터가 오길 기다립니다.
const userItem = await screen.findByText('Leanne Graham (MSW)');
console.log('[Test 1] 데이터 수신 완료');
screen.debug();
expect(userItem).toBeInTheDocument();
});
// [추가] 에러 상황 테스트
test('서버 에러 발생 시 에러 문구가 표시된다', async () => {
// 1. 이 테스트 동안만 서버가 500 에러를 뱉도록 설정을 덮어씁니다(Override).
server.use(
http.get('https://jsonplaceholder.typicode.com/users', () => {
return new HttpResponse(null, { status: 500 });
})
);
render(<UserList />);
console.log('[Test 2] 로딩 상태 확인');
expect(screen.getByText('불러오는 중...')).toBeInTheDocument();
console.log('[Test 2] 에러 발생 대기');
// 2. 컴포넌트가 에러를 감지하고 "에러가 발생했습니다."를 띄울 때까지 기다립니다.
const errorMsg = await screen.findByText('에러가 발생했습니다.');
console.log('[Test 2] 에러 UI 렌더링 확인');
screen.debug();
expect(errorMsg).toBeInTheDocument();
});
});npm test -- src/components/UserList.test.tsx
[Test 1] 로딩 상태 확인
[Test 1] MSW 정상 응답 대기
[Test 1] 데이터 수신 완료
<body>
<div>
<ul>
<li>Leanne Graham (MSW)</li>
<li>Ervin Howell (MSW)</li>
</ul>
</div>
</body>
[Test 2] 로딩 상태 확인
[Test 2] 에러 발생 대기
[Test 2] 에러 UI 렌더링 확인
<body>
<div>
<p>에러가 발생했습니다.</p>
</div>
</body>
PASS src/components/UserList.test.tsxserver.use 덕분에 500 에러를 받고 에러 메시지를 보여주었습니다.
우리는 실제 서버를 끄거나 코드를 망가뜨리지 않고도, 에러 상황을 검증해냈습니다.git clone https://github.com/PROMLEE/my-tdd-app.git
cd my-tdd-app
git checkout part9handlers.ts 한 곳에서 API 응답을 관리하여 유지보수성을 높였습니다.setupTests.ts 설정을 통해 모든 테스트 파일에서 자동으로 가짜 서버가 동작하도록 만들었습니다.server.use를 사용하여 특정 테스트에서만 강제로 에러를 발생시키고, 컴포넌트의 대처 능력을 검증했습니다.다음 시간에는10편. Form 입력과 Validation 테스트를 통해, 사용자가 텍스트를 입력하고 전송 버튼을 누르는 과정을 테스트하는 방법을 알아보겠습니다.