
MSW๋ฅผ ํ์ฉํ API ๋ชจํน๊ณผ ํ ์คํธ์ฝ๋

- #MSW
- #test
์๋ ํ์ธ์. ๋ ธ๋จธ์ค ํ๋ก ํธ์๋ ํ ๊น๋ฏผ์ฃผ์ ๋๋ค.
์ด๋ฒ ๊ธ์์๋ MSW๋ฅผ ๋์ ํ๊ณ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด์ ๋ฐฐ์ด ๋ด์ฉ์ ๊ณต์ ํด๋ณด๋ ค๊ณ ํฉ๋๋ค.
MSW ๋์ ๋ฐฐ๊ฒฝ
MSW๋ฅผ ๋์ ํ๊ฒ ๋ ๊ณ๊ธฐ๋ ๋ฉ์ถฐ ์๋ ํ๋ก ํธ์๋ ํ ์คํธ ์ฝ๋๋ฅผ ๋ค์ ์์ฑํ๋ฉด์์์ต๋๋ค.
์ ํฌ ํ๋ก์ ํธ๋ ์ฌ์ฉ์ ๊ถํ์ ๋ฐ๋ผ ๋ค์ํ UI๊ฐ ๋ ธ์ถ๋๊ณ , ๊ถํ๋ณ๋ก ๋ค๋ฅธ API ํธ์ถ์ด ๋ฐ์ํ๋ ๊ตฌ์กฐ์ ๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์์์น ๋ชปํ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ์ปธ๊ณ ํ ์คํธ ์ฝ๋๋ ํ์์ ์ด์์ต๋๋ค.
๊ธฐ์กด์ ์์ฑ๋ ํ ์คํธ ์ฝ๋๊ฐ ์๊ธด ํ์ง๋ง, ์๊ฐ์ด ์ง๋๋ฉด์ ๋ก์ง์ด ์์ ๋๊ฑฐ๋ API ์๋ต์ด ๋ฐ๋๋ฉด์ skip ์ฒ๋ฆฌ๋ ์ฝ๋๋ ๋ง์๊ณ ์๋ก์ด ์ผ์ด์ค๊ฐ ์ถ๊ฐ๋๋ฉด์ ์์ ๊ณผ ๋ณด์์ด ํ์ํ์ต๋๋ค.
์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด, API ํธ์ถ์ ๊ฐ๋ก์ฑ ์ํ๋ ์๋ต์ ๋ชจํนํ ์ ์๋ MSW๋ฅผ ๋์ ํ๊ฒ ๋์์ต๋๋ค.
MSW(Mock Service Worker)๋?
: MSW(Mock Service Worker)๋ย ์๋น์ค ์์ปค(Service Worker) ๊ธฐ์ ์ ํ์ฉํ์ฌ ๋คํธ์ํฌ ๋ ๋ฒจ์์ API ์์ฒญ์ ๊ฐ๋ก์ฑ๊ณ ๋ชจํน(mocking)ํ ์ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
ํนํ, ํ ์คํธ ํ๊ฒฝ์์ API ์์ฒญ์ ์ ์ดํ๊ฑฐ๋, ๋ก์ปฌ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์์ ์์์ API ์๋ต์ ์ค์ ํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค.
MSW ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ฌ ํ๋ก์ ํธ์ ์ค์นํ๊ณ ํ์ํ ์ค์ ํ์ผ๋ค์ ์ธํ ํ์ต๋๋ค. ์ด์ MSW๋ฅผ ์ด์ฉํด ์ปดํฌ๋ํธ ํ ์คํธ๋ฅผ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค.
ํ ์คํธ ์ผ์ด์ค ์ ์
- ์ฑ๋์ ์ฐ๊ฒฐ๋ ๋ฉค๋ฒ์ญ์ด ์์ ๋
- ๋ด๊ฐ ๊ฐ์ ํ ๋ฉค๋ฒ์ญ์ด ์์ ๋
- ๋ด๊ฐ ๊ฐ์ ํ ๋ฉค๋ฒ์ญ์ด ์์ ๋
- ๊ฐ์ ํ ๋ฉค๋ฒ์ญ์ด ํ๋์ผ ๋
- ๊ฐ์ ํ ๋ฉค๋ฒ์ญ์ด ์ฌ๋ฌ ๊ฐ์ผ ๋
์ด ํ
์คํธ ์ผ์ด์ค๋ค์ MembershipCardList
์ปดํฌ๋ํธ์ ๋์์ ํ์ธํ๊ธฐ ์ํด ํ์์ ์ด์์ต๋๋ค.
// MembershipCardContainer/index.tsx
const MembershipCardContainer =() => {
const { memberships } = useGetMembershipUsers();
if (!memberships) return <EmptyLinkedMembership />;
if (!memberships.user) return <EmptyJoinedMembership />;
return <MembershipCardListContainer />;
};
MembershipCardList
์ปดํฌ๋ํธ๋ ์กฐ๊ฑด์ ๋ฐ๋ผ ์ธ ๊ฐ์ง ์ปดํฌ๋ํธ ์ค ํ๋๋ฅผ ๋ฐํํ๊ณ ์์ต๋๋ค. ๋ฐ๋ผ์ API ์๋ต๋ณ๋ก ๋ถ๋ฆฌํ์ฌ ํ
์คํธํ๋ ๊ฒ์ด ์ค์ํ์ต๋๋ค.
์ปดํฌ๋ํธ ํ ์คํธ
ํ ์คํธ๋ ๋ค์๊ณผ ๊ฐ์ ์์๋ก ์งํํ์ต๋๋ค.
MembershipContainer/index.test.tsx
- API ์๋ต์ ๋ฐ๋ผ ์กฐ๊ฑด๋ณ๋ก ๋ค๋ฅธ ์ปดํฌ๋ํธ๊ฐ ๋ฐํ๋๋ ๊ตฌ์กฐ์ด๊ธฐ ๋๋ฌธ์, ํ
์คํธ ์
server.use()
๋ก API ๋ชจํน์ ์ค๋ฒ๋ผ์ด๋ํ์ต๋๋ค.- ์๋ต์ด ๋น ๋ฐฐ์ด์ผ ๋
- ๋ด๊ฐ ๊ฐ์ ํ ๋ฉค๋ฒ์ญ์ด ์์ ๋
- ๋ด๊ฐ ๊ฐ์ ํ ๋ฉค๋ฒ์ญ์ด ์์ ๋
// MembershipContainer/index.test.tsx
describe('MembershipContainer', () => {
test('๋ฉค๋ฒ์ญ์ด ์์ ๋ "๋ฉค๋ฒ์ญ์ด ์์ด์" ๋ฌธ๊ตฌ๊ฐ ๋
ธ์ถ๋๋ค', async () => {
server.use(
http.get(`${MembershipApiBaseUrl}/membershipUsers*`, () =>
HttpResponse.json({
success: true,
data: EmptyMemberships
})
)
);
...
test('ํด๋น ๋ฉค๋ฒ์ญ์ ํฌ๋ค๋ช
์ด ์์๋ ํฌ๋ค๋ช
์ด ๋
ธ์ถ๋๋ค', async () => {
server.use(
http.get(`${MembershipApiBaseUrl}/membershipUsers*`, () =>
HttpResponse.json({
success: true,
data: { memberships: [EmptyJoinedMembershipsWithFandomName] }
})
)
);
MembershipCardListContainer/index.test.tsx
- ํด๋น ์ปดํฌ๋ํธ๋ ๋ด๊ฐ ๊ฐ์
ํ ๋ฉค๋ฒ์ญ์ด ์์ ๋ ๋ฐํ๋๋ ์ปดํฌ๋ํธ์ props๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌ๋ฐ์ ๋ ๋๋ง๋ฉ๋๋ค. ๋ฐ๋ผ์ ๋์ผํ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๋ฉด์ props ๊ฐ๋ง ๋ฐ๊พธ์ด ๋ค์ํ UI ์ํ๋ฅผ ํ
์คํธํ์ต๋๋ค.
- ๋ง๋ฃ๋ ๋ฉค๋ฒ์ญ์ผ ๊ฒฝ์ฐ ๋ฉค๋ฒ์ญ ๊ธฐ๊ฐ ๋ง๋ฃ ํ ์คํธ ๋ ธ์ถ
- ๋ชจ์ง์ด ์ข ๋ฃ๋ ๋ฉค๋ฒ์ญ์ผ ๊ฒฝ์ฐ โ๋ฉค๋ฒ์ญ ๋ชจ์ง ์ข ๋ฃโ ๋ฒํผ ๋ ธ์ถ ํ์ธ
- โฆ
// MembershipCardListContainer/index.test.tsx
describe('MembershipCardListContainer', () => {
test('๋ง๋ฃ๋ ๋ฉค๋ฒ์ญ์ผ ๊ฒฝ์ฐ ๋ฉค๋ฒ์ญ ๊ธฐ๊ฐ ๋ง๋ฃ ํ
์คํธ ๋
ธ์ถ', async () => {
render(<MembershipCardListContainer memberships={ExpiredSingleMembership} />);
...
test('๋ชจ์ง์ด ์ข
๋ฃ๋ ๋ฉค๋ฒ์ญ์ผ ๊ฒฝ์ฐ "๋ฉค๋ฒ์ญ ๋ชจ์ง ์ข
๋ฃ" ๋ฒํผ ๋
ธ์ถ', async () => {
render(<MembershipCardListContainer memberships={SingleMembershipRecruitEndAt} />);
...
์ด๋ ๊ฒ MSW๋ฅผ ํ์ฉํด ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด๋ดค์ต๋๋ค.
MSW๋ฅผ ์ฌ์ฉํ๋ API ์์ฒญ์ ๋คํธ์ํฌ ๋ ๋ฒจ์์ ๊ฐ๋ก์ฑ ์ ์์ด ์ค์ API ํธ์ถ๊ณผ ๋ถ๋ฆฌ๋ ํ๊ฒฝ์์ ํ ์คํธ์ฝ๋๋ฅผ ์์ฑ ํ ์ ์์์ต๋๋ค.
๋ํ server.use()
๋ก API ์๋ต์ ์ค๋ฒ๋ผ์ด๋ํด ๋ค์ํ ์ผ์ด์ค๋ฅผ ํ
์คํธํ ์ ์์๊ณ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์์๋ ๋ชจํน๋ API ์์ฒญ๊ณผ response๋ฅผ ํ์ธํ ์ ์์์ต๋๋ค.
์ด๋ฐ ๋ถ๋ถ์ ๋์ค์ API๊ฐ ์ค๋น๋์ง ์์ ์ํฉ์์ ์์ ์ด ํ์ํ ๋ ์ผ์ด์ค๋ณ UI๋ฅผ ํ์ธํด๋ณด๊ฑฐ๋ ํ ์คํธ์ฝ๋๋ฅผ ์์ฑํ ๋ ํฐ ์ฅ์ ์ด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
MSW ํ์ฉ ํ ์คํธ ์ฝ๋ ์์ฑ ์ ๊ณ ๋ ค ์ฌํญ ๋ฐ ๊ฐ์ ๋ฐฉ๋ฒ
์ด ์ธ์๋ MSW๋ฅผ ์ฒ์ ์ฌ์ฉํ๋ฉด์ ๊ฒช์ ์ด๋ ค์๊ณผ ์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ด์ฉ๊ณผ ๋๋ถ์ด ํ ์คํธ ์ฝ๋๋ฅผ ์ง๋ณด๋ฉด์ ๋๊ผ๋ ๋ด์ฉ์ ๊ณต์ ํด๋ณด๋ ค๊ณ ํฉ๋๋ค.
- ํ์ํ mockData์ ๋ชจ๋ ์ผ์ด์ค๋ฅผ ํ๋์ฉ ์ ์ํ๊ธฐ๋ณด๋จ ๋จผ์ ์ ์ํด๋ ๋ฐ์ดํฐ๋ฅผ ํ์ฉํด์ ์ฌ์ฌ์ฉ โ
export const SingleMembershipWithFandomName = [
{
...ExpiredSingleMembership[0],
user: undefined,
fandomName: '๋
ธ๋จธ์ค'
}
];
- test ํ์ผ์์ ์กฐ๊ฑด๋ฌธ์ ์ฐ๊ธฐ๋ณด๋ค ๊ฐ ์กฐ๊ฑด์ ๋ง๋ mockData ์ค๋น
โ
if(membership.name) {
const name = await screen.findByText(membership.name);
expect(name).toBeInTheDocument();
}
โญ๏ธ
export const SingleMembershipWithName = [
{
...ExpiredSingleMembership[0],
user: undefined,
name: '๋
ธ๋จธ์ค'
}
];
test('์ด๋ฆ์ด ์์๊ฒฝ์ฐ ์ด๋ฆ ๋
ธ์ถ ํ์ธ', async () => {
render(<MembershipCardListContainer memberships={SingleMembershipWithName} />);
const name = await screen.findByText(SingleMembershipWithName[0].Name);
expect(name).toBeInTheDocument();
});
- mockData๋ ์์์ด๋ฏ๋ก Pascalcase ์ฌ์ฉ(์ปจ๋ฒค์ ์ ๋ง๊ฒ)
- ํ ์คํธํ๊ณ ์ ํ๋ ์ปดํฌ๋ํธ๋ฅผ ์ง์ render ํด์ ํ ์คํธ ์งํ
โ
render(
<>
<MembershipCardEmpty />
<MembershipJoinButton />
<MembershipPromotion />
</>
);
โญ๏ธ
render(<EmptyLinkedMembership />);
: ์ปดํฌ๋ํธ ์์ฒด๋ฅผ ํ ์คํธํ๋ ๊ฒ์ ์ปดํฌ๋ํธ์ ๊ตฌํ ๋ฐฉ์์ด ๋ฐ๋์ด๋ ํ ์คํธ๊ฐ ๋์ผํ๊ฒ ์๋ํ๋๋ก ๋ณด์ฅํด ์ค๋๋ค.
- mockServiceWorker.jsํ์ผ์ propduction์ ๋น๋์ ์ ์ธ ํ์(https://github.com/mswjs/msw/issues/291)
- MSW๊ฐ 2.0์ผ๋ก ์ ๋ฐ์ดํธ ๋์ผ๋ ํด๋น ๋ด์ฉ ํ์ธํ๊ธฐ(https://mswjs.io/blog/introducing-msw-2.0/)
์๋๋ MSW๋ฅผ ์ฌ์ฉํ๋ฉด์ ๋ง์ฃผํ ์๋ฌ์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋๋ค.
์๋ฌ ์ฒ๋ฆฌ
- ReferenceError: BroadcastChannel is not defined
- ์์ธ :
BroadcastChannel
API๊ฐ Node.js ํ๊ฒฝ์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณต๋์ง ์๊ณ ํด๋น ์์ฑ์ Node.js 18 ๋ฒ์ ๋ถํฐ ์์ต๋๋ค. - ํด๊ฒฐ : git flow push yamlํ์ผ์ ์ธํ ๋ node version์ 16์์ 18๋ก ์ฌ๋ ค์ ํด๊ฒฐํ ์ ์์์ต๋๋ค. (์ฐธ๊ณ ํ ๋งํฌ๋ฅผ ๋ณด๋ฉด jset+jsdom ํ๊ฒฝ์ผ๋ jest-fixed-jsdom ๋ก๋ ํด๊ฒฐ ๊ฐ๋ฅํด ๋ณด์ ๋๋ค)
- ์์ธ :
- server.use()๋ก api ๋ชจํน ์์ ์ด ๋์ง ์์ ๋
- ์์ธ : react-query๋ก api๋ฅผ ํธ์ถํ๊ณ ์๋๋ฐ ์ด์ ๋ฐ์ดํฐ๊ฐ ์บ์์ ๋จ์์์ด ๋ฐ์ํ๋ ๋ฌธ์
- ํด๊ฒฐ : setupTestํ์ผ์ React Query cache๋ฅผ resetํ๋ ์ฝ๋ ์ถ๊ฐํด์ ํด๊ฒฐํ ์ ์์์ต๋๋ค.
beforeEach(() => {
queryCache.clear();
});
or
afterEach(() => {
server.resetHandlers();
queryClient.clear();
});
๋ ผ์ ์ฌํญ
- MSW ํด๋ ๊ตฌ์กฐ
: MSW๋ฅผ ๊ฒ์ํด๋ณด๋ฉด ํด๋ ๊ตฌ์กฐ๋ฅผ API์ฃผ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ตฌ์ฑํ๋ ๊ณณ๋ ์์๊ณ , handler์ api๋ฅผ ๊ฐ๊ฐ ๋ถ๋ฆฌํด์ ๊ด๋ฆฌํ๋ ๊ณณ๋ ์์์ต๋๋ค. ํ์ง๋ง ์ด๋ ๋ชจ๋ mocksํ์ผ ๋ด๋ถ์ ์์๊ณ ์ ํฌ ํ๋ก์ ํธ์๋ ๋ฐฉํฅ์ด ๋ค๋ฅด๋ค๊ณ ์๊ฐํ์ต๋๋ค.
- testํ์ผ๊ณผ mockDataํ์ผ์ ํด๋น ์ปดํฌ๋ํธ์ ๋์ผํ ๋ ๋ฒจ์ ์์น
- mocksํด๋ ์์๋ node settingํ์ผ๊ณผ browser settingํ์ผ๋ง ์์น
์ ํฌ๋ ์ด๋ ๊ฒ ๊ตฌ์กฐ๋ฅผ ์ค์ ํด ๋ดค์ต๋๋ค. ๊ณต์ ๋ฌธ์์๋ src/mocks/handler.js์ src/mocks/node.js๋ง ์ ํ์๊ธฐ ๋๋ฌธ์ ๊ฐ์ธ์ด๋ ํ์ฌ์ ๋ง๋ ๊ตฌ์กฐ๋ก ๋ง๋ค์ด๊ฐ์๋ฉด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
๋ง์น๋ฉฐ
์ด๋ฒ ์์ ์ ํตํด MSW๋ฅผ ๋์ ํด ์ฒ์์ผ๋ก ํ ์คํธ ์ฝ๋๋ฅผ ์ง๋ณผ ์ ์์์ต๋๋ค. ์ด๋ฒ์๋ MSW๋ฅผ ์ถฉ๋ถํ ํ์ฉํด๋ณด์ง ๋ชปํ์ง๋ง ์์ผ๋ก ์์ฑํ๋ ํ ์คํธ ์ฝ๋์์๋ MSW๋ฅผ ๋ ๋ง์ด ํ์ฉํด๋ณผ ์ ์์ผ๋ฉด ์ข๊ฒ ์ต๋๋ค.
MSW๋ฅผ ํ์ฉํ ํ ์คํธ์ ๊ด์ฌ์ด ์์ผ์ ๋ถ๋ค์๊ฒ ์ด ๊ธ์ด ๋์์ด ๋๊ธธ ๋ฐ๋ผ๋ฉฐ ๊ธ ๋ง์น๊ฒ ์ต๋๋ค.
๊ฐ์ฌํฉ๋๋ค:)
[์ฐธ๊ณ ๋ธ๋ก๊ทธ]