Before getting started ๐
In Part 8, we intercepted
global.fetch directly for tests.
That approach requires redefining it repeatedly and does not perfectly reproduce real network behavior.
Today we will introduce
MSW
, Mock Service Worker, often called the
de facto standard
for frontend testing.
It intercepts network requests, allowing tests for both successful responses and
server errors such as HTTP 500
.
Step 1: Installing and configuring MSW ๐
Install the library after navigating to the project directory in the terminal.
Terminal
A
handler
defines a rule: Respond this way when a request arrives at a particular URL.
Create
src/mocks and add
handlers.ts.
src/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)' },
]);
}),
];
Configuring the test server ๐
Our tests run in the terminal, in Node.js, rather than in the browser.
We therefore need a
Node.js server
rather than a browser worker.
Create
src/mocks/server.ts.
src/mocks/server.ts import { setupServer } from 'msw/node' ;
import { handlers } from './handlers' ;
// ์์์ ๋ง๋ ํธ๋ค๋ฌ๋ค์ ๋ฌถ์ด์ ๊ฐ์ง ์๋ฒ๋ฅผ ์์ฑํฉ๋๋ค.
export const server = setupServer ( ... handlers);
Step 2: Applying MSW to the test environment ๐
Configure the mock server to start before tests and stop afterward.
Update the previously created
src/setupTests.ts.
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 ());
Step 3: Adding component error handling ๐
To test errors, the component must display something when one occurs.
Update
src/components/UserList.tsx to handle an error state.
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 >
);
}
Step 4: Testing successful and failed requests ๐
Update
UserList.test.tsx.
Remove the previous
global.fetch mocking code and use
MSW
for success and failure cases.
The important point is that
MSW's server.use can override a handler for just one test
.
src/components/UserList.test.tsx 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 ();
});
});
Step 5: Running and analyzing tests ๐
Run the tests from the terminal.
Terminal npm test -- src/components/UserList.test.tsx
Checking the execution log ๐
Checking the execution log
[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.tsx
The logs show that
Test 1
fetched the list normally, while
Test 2
received a 500 response and displayed an error message because of
server.use.
We verified the error path without shutting down the real server or breaking application code.
Terminal git clone https://github.com/PROMLEE/my-tdd-app.git
cd my-tdd-app
git checkout part9
MSW has improved our API-testing strategy.
Introducing MSW
Managing API responses centrally in handlers.ts improves maintainability.
Automatic setup
The setupTests.ts configuration makes the mock server available automatically to all test files.
Error simulation
server.use forces an error in an individual test to verify the component's response.
We now understand tests that fetch data with GET.
Forms that accept user input and send it to the server with POST matter too.
Next,
Part 10: Form Input and Validation Tests
will test entering text and pressing a submit button.