Jest ๋ ์๋ฐ์คํฌ๋ฆฝํธ ํ ์คํธ ํ๋ ์์ํฌ๋ก , ๊ณต์ ๋ฌธ์์์๋ ๋จ์์ฑ์ ์ด์ ์ด ๋ง์ถฐ์ ธ ์๋ค๊ณ ๊ฐ์ฅ ๋จผ์ ์๊ฐํ๊ณ ์์ต๋๋ค. ์ด ๋ง์ฒ๋ผ ๋๋ถ๋ถ์ ์๋ฐ์คํฌ๋ฆฝํธ ํ๋ก์ ํธ์์ ํฐ ์ค์ ์์ด ๋ฐ๋ก ์คํํ ์ ์์ผ๋ฉฐ , mocking ๋ฑ์ด ์ฝ๊ณ ํ ์คํธ๊ฐ ๋น ๋ฅด๊ฒ ์คํ๋์ด ๋ง์ ํ๋ก์ ํธ์์ ์ฌ๋๋ฐ๊ณ ์์ต๋๋ค.
์ด์ ๋น๊ต๋๋ ํ๋ ์์ํฌ๋ก vite ์์๋ vitest ๊ฐ ๋ ์ค๋ฅด๊ณ ์์ต๋๋ค. ์ด๋ฆ์์ ๋ณด์ด๋ฏ vite ์ ๊ต์ฅํ ์ข์ ํธํ์ฑ์ ์๋ํ๋ฉฐ , ๊ณต์ ๋ฌธ์์์๋ It's fast! ๋ผ๊ณ ์ ํ์๋ฏ ๋น ๋ฅด๋ค๊ณ ์๊ฐํ๊ณ ์์ต๋๋ค. ์ญ์ ๋ง์ ์ฌ๋์ ๋ฐ๊ณ ์์ผ๋ฉฐ vite ๊ฐ ๋ ๋ง์ด ์ฌ์ฉ๋๋ค๋ฉด , vitest ๋ ๊ฐ์ด ์ฌ๋๋ฐ์ ๊ฒ ๊ฐ์ต๋๋ค. ๋ค๋ง ์ฌ๋ฌ ์ด์ ๋ก ์ธํด์ ์์ง์ jest๊ฐ ๋ง์ด ์ฌ์ฉ๋๊ณ ์๋๋ฐ , ์ฌ์ฉ ๊ฒฝํ์์ ๊ฐ์ด ์ ๋๋ก ํ๊ฒ ์ต๋๋ค.
์ด์ ํ์ฌ์์ ํ ์คํธ ์ฝ๋๋ฅผ ์ฑ์๋ฃ๊ธฐ ์์ํ ๋ ๋ถํฐ ์์ฐ์ค๋ฝ๊ฒ Jest ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ๊ฐ์ต๋๋ค. ์ฌ์ค ์ฒ์ ์ฌ์ฉํ ๋๋ ์ด๋ ๋ค ํ ๋์ฒด ํ๋ ์์ํฌ๊ฐ ์กด์ฌํ์ง ์์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ค CRA ์์ Vite ๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ , monorepo ๋ ์ ์ฉํ๋ ๊ณผ์ ์์ Vitest ๋ฅผ ๋ฐ๊ฒฌํด์ ์์ฐ์ค๋ฝ๊ฒ ์ ์ฉํด ๋ณด๋ ค๊ณ ์๋ํ ๊ฒฝํ์ด ์์ต๋๋ค. ํ์ง๋ง ๊ฒฐ๊ตญ์ ๋ค์ Jest ๋ก ๋์์๋๋ฐ ์ฌ๋ฌ ๋น๊ต์ ์์ Jest ๊ฐ ์์ง์ ์ฌ์ฉํ๊ธฐ ํธ๋ฆฌํ๋ค๋ ์๊ฐ์ด ๋ค์ด์์์ต๋๋ค. ์ ์ฉํด ๋ณธ ํ ์ฅ๋จ์ ์ ์ ์ด๋ณด๋ฉด,
Vite ํ๊ฒฝ์์๋ Vitest ์ค์ ์ด ๋ ์ฌ์ ์ต๋๋ค. Jest ์ ํน์ฑ์ Babel ์ค์ ๋ฑ ์ฌ๋ฌ ์ค์ ์ ๊ณ๋ค์ฌ์ผ ํ์ง๋ง , vitest ๋ ๊ทธ๋ฐ ์ค์ ์ด ํ์ ์์ด vite ์ ํธํ๋๋๋ก ๊ฐ๋ฐ๋ ์ ์ด ์ข์์ต๋๋ค.
@vitest/ui ๋ฅผ ํตํด์ ๋์๋ณด๋ ํํ๋ก ํ
์คํธ์ ๋ํด ๋ณผ ์ ์๋ ๋ถ๋ถ์ด ํธ๋ฆฌํ์ต๋๋ค. ๋ฌผ๋ก jest ์์๋ ๊ฐ๋ฅํ๊ณ shell์์ ๋ณด๋ ๊ฒ๋ ๊ฐ๋ฅํ์ง๋ง , ํจ์ฌ ์ง๊ด์ ์ผ๋ก ๋ณผ ์ ์๋ ๋ถ๋ถ์ด ์์์ต๋๋ค.
๋ค๋ง ๊ธฐ์กด Jest ์ mocking์ ์ต์ํด์ ธ ์๋ค๋ฉด , vitest ์์๋ ๋ ์ฌ์ธํ๊ธฐ mocking์ ํด์ฃผ์ด์ผ ํ๋ ๋ถ๋ถ์ด ์์ต๋๋ค. jest ๊ฐ ์กฐ๊ธ ๋ ๋๋ํ๊ฒ ๋ณด๋ ๋๋ ? ํ์ง๋ง ์ด ๋ถ๋ถ์ vitest ์ ํ ์คํธ๊ฐ ๋ ๋จ๋จํ๊ธฐ ๋๋ฌธ์ ์คํ๋ ค ๋ ๊ฒฌ๊ณ ํ ํ ์คํธ ์ฝ๋๊ฐ ์์ฑ๋๋ ๋๋๋ ์์์ต๋๋ค. ํ์ง๋ง ์ฒ์์๋ ์ ์์ด ์ด๋ ต๊ณ , ๊ธฐ์กด ์ฝ๋๋ฅผ ์์ ํ๋ ๋ถ๋ถ์์ ์๊ฐ์ด ๋ง์ด ๋๋ ๋จ์ ์ด ์์์ต๋๋ค.
์ ์ฉ์ ์๋ํ ๋์๋ vitest ์ ๋ํ ์ ๋ณด๊ฐ ๋ง์ด ๋ถ์กฑํด์ , ์ ์ฉํ๋ค ์ค๋ฅ๊ฐ ๋๋ ๊ฒฝ์ฐ ์ ๋ณด๋ฅผ ์ฐพ๊ธฐ๊ฐ ๊ต์ฅํ ํ๋ค์์ต๋๋ค. ์ธํ ์ด ํ์ํ ๋ถ๋ถ์์๋ ์ค๋ฅ๊ฐ ๋๋ค๋ฉด , ์ ์ฉํ๊ธฐ ๊ต์ฅํ ํ๋ค์์ต๋๋ค.
๊ฐ์ฅ ์ค์ํ ์ด์ ๋ก , vitest ์ ์ฒซ ๋์ ๋ชฉ์ ์ ํ ์คํธ ์คํ ์๋ ํฅ์์ด์์ต๋๋ค. ํ ์คํธ ์ฝ๋์ ํฌ๊ธฐ๊ฐ ์ปค์ ธ์ ํ ์คํธ ์ฝ๋๋ฅผ ํ๋ฒ ์คํํ๋ฉด 5 ~ 10๋ถ๋์ ๋์๊ฐ๋ ์ํฉ์ด์๊ธฐ ๋๋ฌธ์ ์๊ฐ ๋จ์ถ์ด ๊ฐ์ ํ์ต๋๋ค. ๊ทธ๋์ vitest ๋ฅผ ์ ์ฉ์์ผฐ๋๋ฐ , ์ค์ ๋ก๋ ์คํ๋ ค jest๊ฐ ๋ ๋น ๋ฅธ ์ํฉ์ด ๋์์ต๋๋ค. Vitest๋ ์ค๋ ๋๋ฅผ ์ฌ์ฉํ์ฌ Jest๋ณด๋ค 3๋ฐฐ ๋๋ฆฌ๊ฒ ํ ์คํธ๋ฅผ ์คํํฉ๋๋ค. Vitest๋ Jest๋ฅผ ๋์ฒดํ ์ค๋น๊ฐ ๋์ด ์์ง ์์ต๋๋ค.
4๋ฒ๊ณผ 5๋ฒ์ ์ด์ ๊ฐ ๊ฐ์ฅ ํฐ ์ด์ ์๋๋ฐ , ๊ฒฐ๊ตญ์ vitest ๋ฅผ ๋์ ํ์ง ๋ชปํ๊ณ jest ๋ก ๋์์จ ๊ฒฝํ์ด ์์ต๋๋ค. ๋ค๋ง ํ์ฌ๋ vitest ์ ์ํ๊ณ๋ ๋ง์ด ๋ฐ์ ํ ๊ฒ ๊ฐ์์ ๊ธฐํ๊ฐ ๋๋ค๋ฉด vitest ๋ฅผ ์ ์ฉํด๋ณด๊ณ ์ถ์ ๋ง์๋ ์์ต๋๋ค.
ํ์ฌ msw ์ ์ต์ ๋ฒ์ ์ v2 ๋ฒ์ ์ธ๋ฐ , v2 ๋ฒ์ ์ผ๋ก ์ ์ฉ ์ค ๊ธฐ์กด๊ณผ ํธํ๋์ง ์์์ ๋ฐ์ํ๋ ์๋ฌ๊ฐ ๋ง์ด ์์์ต๋๋ค. ( ex. msw/node module can't find error ๋ฑ ) ๋ฐ๋ผ์ ์ด์ ๊ฒ์๊ธ์์๋ v2 ๋ฒ์ ์ด์์ง๋ง , ๊ธฐ์กด์ ์ฌ์ฉํ๋ v1 ๋ฒ์ ์ผ๋ก ์์ ํด์ ์ ์ฉํ์ต๋๋ค.
package.json ์์ ๊ธฐ์กด msw ์ ๋ฒ์ ์ด 2.x.x ์๋๋ฐ ( ์ ๋ 2.3.1 ์ฌ์ฉ์ค์ด์์ต๋๋ค. ) , ํด๋น ๋ฒ์ ์ 1.2.1 ๋ฒ์ ์ผ๋ก ์์ ํด์ install ํฉ๋๋ค.
json
"msw": "^2.3.1" -> "msw": "^1.2.1",
browser.ts ์์ setupWorker ์ import ๋ฅผ ์์ ํฉ๋๋ค. v2 ์์๋ msw/browser ์์ ๊ฐ์ ธ์์ง๋ง , v1 ๋ฒ์ ์์๋ msw ์์ ๋ฐ๋ก import ํฉ๋๋ค.
ts
// import { setupWorker } from "msw/browser"; <- v2
import { setupWorker } from 'msw'; // <- v1
๋ํ v2 ์์๋ rest API mocking ์ ์ํด์ http, HttpResponse ๋ฅผ ์ฌ์ฉํ์ง๋ง v1 ์์๋ rest ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด์ ๋ฐ๋ผ์ ์ฝ๋๋ฅผ ์์ ํฉ๋๋ค.
ts
// v2
http.get("/fake", () => {
return HttpResponse.json({
data: "data1",
});
}),
// v1
rest.get("/fake", (_, res, ctx) => {
return res(ctx.status(200), ctx.json({ data: "data1" }));
}),
shell
# jest ์ค์น
yarn add -D jest @types/jest
# react testing library ๋ค์ด๋ก๋
yarn add -D @testing-library/jest-dom @testing-library/react @testing-library/user-event @testing-library/react-hooks jest-environment-jsdom @testing-library/dom
@testing-library/jest-dom : Dom ๋
ธ๋๋ฅผ ํ
์คํธํ ๋ ์ฌ์ฉ๊ฐ๋ฅํ matcher ๋ค์ ์ ๊ณตํฉ๋๋ค. ( ex. toBeInTheDocument ๋ฑ )@testing-library/react : React ์ปดํฌ๋ํธ๋ฅผ ํ
์คํธํ๊ธฐ ์ํ ๋๊ตฌ๋ค์ ์ ๊ณตํฉ๋๋ค. ( ex. render ๋ฑ )@testing-library/user-event : ๋ฐ์ํ๋ ์ด๋ฒคํธ ๋ฑ์ ํ
์คํธํ๊ธฐ ์ํ ๋๊ตฌ๋ค์ ์ ๊ณตํฉ๋๋ค. ( ex. click type ๋ฑ )@testing-library/react-hooks : React hooks ๋ฅผ ํ
์คํธํ๊ธฐ ์ํ ๋๊ตฌ๋ค์ ์ ๊ณตํฉ๋๋ค. ( ex. renderHook , act ๋ฑ )~~ ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ @testing-library/react ๋ก ํตํฉ๋์์ต๋๋ค.jest-environment-jsdom : ํ
์คํธ ํ๊ฒฝ์ jsdom ์ผ๋ก ์ค์ ํ๊ธฐ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค.@testing-library/dom : dom ํ
์คํธ์ ๋ํ matcher , util ๋ฑ์ ์ ๊ณตํฉ๋๋ค. ( ex. getByText ๋ฑ )์ดํ package.json ์์ ํ
์คํธ๋ฅผ ์คํํ๊ธฐ ์ํ script ๋ฅผ ์์ฑํฉ๋๋ค.
json
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"test": "jest"
},
์ ์ค์ ํ ๊ฐ๋จํ ํ
์คํธ๋ฅผ ์์ฑ ํ์ ํ
์คํธ๋ฅผ ์คํํ๋ฉด ์๋์ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.

์ ์๋ฌ๋ jest ํ ์คํธ ์คํ ์ Babel ์ธํ ์ด ๋์ด์์ง ์์์ ๋ฐ์ํ๋ ๋ฌธ์ ์ ๋๋ค. Vite ๋ ESM ( ES module ) ์ ์ฌ์ฉํ์ง๋ง , Jest ๋ CommonJS ๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ Babel ์ ์ฌ์ฉํด์ ES module ์ CommonJS ๋ก ๋ณํํด์ผ Jest ๊ฐ ์ธ์ํ๊ณ ์คํ ๊ฐ๋ฅํฉ๋๋ค. ๋ฐ๋ผ์ Babel ์ธํ ์ ์ถ๊ฐํด์ค์ผ ํฉ๋๋ค.
shell
yarn add -D @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
@babel/core : javascript ์ฝ๋๋ฅผ ํธ๋์คํ์ผ๋ง ํฉ๋๋ค. babel์ ํต์ฌ ๋ชจ๋์
๋๋ค.@babel/preset-env : ์ต์ javascript์ ๊ตฌํ javascript ์ฌ์ด์์ ๋ฒ์ ์ด ํธํ๋๋๋ก ์ค์ ํด์ค๋๋ค.@babel/preset-react : jsx ๋ฑ์ react ๋ฌธ๋ฒ์ javascript๋ก ๋ณํํด์ฃผ๋ ์ญํ ์ ํฉ๋๋ค.@babel/preset-typescript : typescript๊ฐ ์ ์ฉ๋๋๋ก ์ค์ ํด ์ค๋๋ค.์ดํ root ์ babel.config.cjs ํ์ผ์ ๋ง๋ค์ด์ ์๋์ ๊ฐ์ด ์์ฑํด ์ค๋๋ค.
js
module.exports = {
presets: [['@babel/preset-env'], ['@babel/preset-react'], '@babel/preset-typescript'],
};
์ถ๊ฐ๋ก jest.config.cjs ํ์ผ๋ ์์ฑํด ์ค๋๋ค. ์ฐธ๊ณ ๋ฌธ์ ์์ ํ์ํ ์ค์ ์ ์ถ๊ฐํด ์ค๋๋ค. ์ฌ๊ธฐ์๋ ํ
์คํธ ํ๊ฒฝ๊ณผ mock clear ์ฒ๋ฆฌ๋ง ์ถ๊ฐํ์ต๋๋ค.
js
module.exports = {
testEnvironment: 'jsdom', // ๊ธฐ๋ณธ๊ฐ์ด jsdom ์ด๋ผ ์ถ๊ฐํ์ง ์์๋ ๋ฌด๊ดํจ
clearMocks: true, // mock clear ์ฒ๋ฆฌ ์ถ๊ฐ
};
์ด์ msw ์ฌ์ฉ์ ์ํด์ setup file ์ ์์ฑํด์ผ ํฉ๋๋ค. ๋ฌผ๋ก setup file ์์ด ๊ฐ ํ ์คํธ ๋ด๋ถ์์ ์ค์ ํด์ค ์ ์์ง๋ง , ๊ทธ๋ ๊ฒ ๋๋ฉด ๋ชจ๋ ํ ์คํธ์์ ์ผ์ผํ ์ ์ธํด์ผ ํ๋ฏ๋ก setup file ์ ํตํด์ ํ๋ฒ์ ์ค์ ํด ์ค ์ ์์ต๋๋ค.
root ์์น์ setupFile ์ ์์ฑํฉ๋๋ค. ์ด๋ฆ์ ์ํ๋ ์ด๋ฆ์ผ๋ก ์์ฑํ๋ฉด ๋๋๋ฐ , ์ ๋ setupTests.ts ๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
ts
import '@testing-library/jest-dom';
import { server } from './src/mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
import "@testing-library/jest-dom" : jest-dom ํ๊ฒฝ์์ ํ
์คํธํ๊ธฐ ์ํด import ํฉ๋๋ค.import { server } from "./src/mocks/server" : ์์ฑํ msw ์๋ฒ๋ฅผ ๋ถ๋ฌ์ต๋๋ค. ๊ทธ ์ดํ
beforeAll ) ์ ์๋ฒ๋ฅผ ์ฐ๊ฒฐ์์ผ ์ฃผ๊ณ afterEach ) ๋ง๋ค handler ๋ฅผ ์ด๊ธฐํํด์ฃผ๊ณ afterAll ์๋ฒ๋ฅผ ๋ซ์ต๋๋ค. )๊ทธ ์ดํ ์ด ํ์ผ์ jest ์ค์ ์ ๋ฃ์ด์ผ ํ๋๋ฐ , jest.config.cjs ํ์ผ์ setupFilesAfterEnv ์ต์
๊ณผ ํจ๊ป ์ถ๊ฐํฉ๋๋ค.
js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
clearMocks: true,
};
์ด์ ํ ์คํธ๋ฅผ ์ํด์ ๊ฐ๋จํ hook ์ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. Riot games ์ Team Fight Tactics ์ open API ๋ฅผ ์ฌ์ฉํด์ ์ฑํผ์ธ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ hook์ ๋ง๋ค์์ต๋๋ค. ์์ธํ ์ฝ๋๋ ์ฌ๊ธฐ ์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
ts
// useGetChampionData.ts
const useGetChampionData = () => {
const [dataChampionData, setChampionData] = useState<Record<string, GetChampionData>>();
const fetchGetChampionData = async () => {
try {
const response = await axios.create().get(TFT_CHAMP_URL);
const { data } = response;
setChampionData(adapter(data.data));
} catch (error) {
console.error('useGetChampData Error : ', error);
}
};
return {
data: dataChampionData,
fetchGetChampionData,
};
};
๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์จ ํ , adapter ๋ฅผ ํตํด์ ํน์ ํ์ํ ๊ฐ๋ง dataChampionData ์ ์ ์ฅํ๋ ํจํด์ ์ฌ์ฉํ์ต๋๋ค. ( ๊ฐ์ธ์ ์ผ๋ก fetch ๋ฑ์์ adapter ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ ํธํฉ๋๋ค. ) ๊ธฐ์กด ๊ฐ์ ธ์ค๋ ๋ฐ์ดํฐ์๋ ํ์ํ์ง ์์ ๊ฐ์ด๋ , ๊ณผ๋คํ ์ ๋ณด๊ฐ ์์ด์ ์ด๋ฅผ ์ถ๋ ค๋ด๋ ๊ณผ์ ์ adapter ๋ฅผ ํตํด์ ์งํํ์ต๋๋ค.
ts
// useGetChampionData.test.ts
import { renderHook, act } from '@testing-library/react-hooks';
import useGetChampionData, { TFT_CHAMP_URL, adapter } from './useGetChampionData';
import { GET_CHAMPION_DATA_RESPONSE } from '../../fixtures/tft';
import { server } from '../../mocks/server';
import { rest } from 'msw';
describe('useGetChampionData', () => {
it('fetchGetChampionData ๊ฐ ์คํ๋๋ฉด , data ๋ฅผ ๊ฐฑ์ ํ๋ค.', async () => {
const { result } = renderHook(useGetChampionData);
expect(result.current.data).toBeUndefined();
await act(async () => {
await result.current.fetchGetChampionData();
});
expect(result.current.data).toEqual(adapter(GET_CHAMPION_DATA_RESPONSE));
});
it('fetchGetChampionData ๊ฐ ์คํจํ๋ฉด , console error ๊ฐ ํธ์ถ๋๋ค. ', async () => {
const mockConsoleError = jest.spyOn(console, ).( );
server.(
rest.(, {
(ctx.());
}),
);
{ result } = (useGetChampionData);
(result..).();
( () => {
result..();
});
(mockConsoleError).();
});
});
@testing-library/react-hooks
renderHook : hook์ ๋
๋ฆฝ์ ์ผ๋ก ๋ ๋๋งํ๊ณ ํ
์คํธํ ์ ์๊ฒ ํด์ฃผ๋ ํจ์์
๋๋ค. hook ์ ์ํ๋ ๋ฐํ๊ฐ์ ๊ฒ์ฌํ ์ ์๊ฒ ํด์ค๋๋ค.act : ์ํ ์
๋ฐ์ดํธ๋ฅผ ๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ฒ ํด์ฃผ๋ ํจ์์
๋๋ค. ํ
์คํธ ์์์ ์
๋ฐ์ดํธ ์ฒ๋ฆฌ ํ ์ํ๋ฅผ ํ์ธํ๋๋ก ๋์์ค๋๋ค.ํจ์ mocking
spyOn : ํน์ method ์ ๋ํด ํธ์ถ์ด ๋์๋์ง , ๋ช๋ฒ ํธ์ถ ๋์๋์ง , ๋ฐํ๊ฐ์ ๋ฌด์์ธ์ง ๋ฑ์ ์ค์ ํ๊ณ ๊ฐ์ํ๋ ์ญํ ์ ํฉ๋๋ค. ์์์๋ ์๋ฌ๊ฐ ๋๋ ๊ฒฝ์ฐ console.error ๋ฅผ ๋ฐํํ๊ฒ ๋๋๋ฐ , ํด๋น console.error ๋ฅผ ๊ฐ์ํ๋ ์ญํ ์ ํฉ๋๋ค.mockImplementationOnce : mocking ํ ํจ์๋ฅผ ๊ตฌํํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์ค์ ํ๋ ์ญํ ์ ํฉ๋๋ค. ํด๋น ํจ์๋ 1๋ฒ๋ง ์คํ๋ฉ๋๋ค.msw
server.use : ์ค์ ๋ server ์ ํน์ method ๋ฅผ mocking ํฉ๋๋ค. ์์ ๊ฒฝ์ฐ , fetch ์ return status ๋ฅผ 400์ผ๋ก ์กฐ์ ํด์ error ๋ฅผ ์ ๋ํ์ต๋๋ค. 400 ์ธ์๋ ์ฌ๋ฌ status ๋ , return ๊ฐ์ ์์ ํด์ ์ํ๋ ํ
์คํธ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.์ ์ํฉ์์ ํ ์คํธ๋ฅผ ์คํํ๋ฉด ์ ์์ ์ผ๋ก ์คํ๋์ง๋ง , ์๋์ ๊ฐ์ warning ๋ช๊ฐ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค.
Warning: `ReactDOMTestUtils.act` is deprecated in favor of `React.act`.
Import `act` from `react` instead of `react-dom/test-utils`.
See https://react.dev/warnings/react-dom-test-utils for more info.
Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead.
Until you switch to the new API, your app will behave as if it's running React 17.
Learn more: https://reactjs.org/link/switch-to-createroot
Warning: unmountComponentAtNode is deprecated and will be removed in the next major release.
Switch to the createRoot API. Learn more: https://reactjs.org/link/switch-to-createroot
ํด๋น ์๋ฌ๋ค์ ์ฐพ์๋ณด๋ , ์ด๋ฏธ ์ด์ ๋ฒ์ ์์ ๋ฐ์ ํ ํด๊ฒฐ๋ ๋ฌธ์ ๋ค์ด๋ผ๊ณ ํ๋๋ฐ ๊ฐ์๊ธฐ ๋ฐ์ํ์ง ? ๋ผ๋ ์๊ฐ์ด ๋ค์๋ค. ๊ทธ๋์ renderHook ๊ณผ act ์์ ๋ฐ์ํ ๋ฌธ์ ๋ผ๊ณ ์๊ฐํด์ @testing-library/react-hooks ์ ๋ํด ์ฐพ์๋ดค๋๋ฐ , ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ด๋ฏธ @testing-library/react ๋ก ํตํฉ๋์๋ค๋ ๊ฒ์ ์ ์ ์์๋ค ! ๊ทธ๋์ react-hooks ๋ฅผ ์ญ์ ํ , ๋ค์ ํ
์คํธ๋ฅผ ์งํํ๋ค.
shell
yarn remove @testing-library/react-hooks
# ์ถ๊ฐ๋ก , ๊ธฐ์กด ํ
์คํธ ํ์ผ์์๋ ์๋์ ๊ฐ์ด ์์ ํฉ๋๋ค.
# import { renderHook, act } from "@testing-library/react-hooks"; ์์
# import { renderHook, act } from "@testing-library/react"; ๋ก ์์ ํฉ๋๋ค.

๊ทธ ํ ์ด ์๋ฌ๊ฐ ๋ฑ์ฅํ๋ค๋ฉด , @testing-library/dom ์ ์ถ๊ฐ๋ก ์ค์นํด์ค๋ค.
shell
yarn add -D @testing-library/dom
๊ทธ ํ ํ ์คํธ๋ฅผ ์งํํ๋ฉด , ์ ์์ ์ผ๋ก ์งํ๋๋ค !
ํ
์คํธ๋ฅผ ์งํํ๋ค๋ณด๋ฉด ์ ์ฒด ํ
์คํธ๊ฐ ์ด๋ค์ง , ์ปค๋ฒ๋ฆฌ์ง๋ ์ผ๋ง์ธ์ง ๋ฑ์ ํ์ธํด์ผ ํ ๋๊ฐ ์์ต๋๋ค. ๋ฌผ๋ก shell ์๋ ๋์ค์ง๋ง , ์ด๋ฅผ ์ ์ฒด์ ์ผ๋ก ํ์ธํ๋ฉด ๋ ์ข์ง ์์๊น์ ? ๊ทธ๋ด ๋ ์ฌ์ฉ๊ฐ๋ฅํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์ jest-html-reporters ๊ฐ ์์ต๋๋ค. ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ
์คํธ์ ๋ํ ์ข
ํฉ์ ๋์๋ณด๋๋ก ์ ๋ฆฌํด์ html ํ์ผ๋ก ์ ๊ณตํฉ๋๋ค.
shell
yarn add -D jest-html-reporters
ํด๋น report ์ ๋ํ ์ค์ ์ jest config ํ์ผ์์ ์งํํฉ๋๋ค. ํญ๋ชฉ๋ณ ๋ด์ฉ์ ๊ณต์ ๋ฌธ์ ์์ ํ์ธ ๊ฐ๋ฅํฉ๋๋ค. ์ ๊ฐ ์ค์ ํ ๋ถ๋ถ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
js
coverageDirectory: "<rootDir>/reports/coverage/",
collectCoverageFrom: [
"**/src/**/*.[jt]s?(x)",
"!**/src/fixtures/**",
"!**/src/mocks/**",
"!**/src/*.[jt]s?(x)",
],
reporters: [
"default",
[
"jest-html-reporters",
{
publicPath: "./reports",
filename: "test_report.html",
pageTitle: "Test Report",
openReport: false,
inlineSource: true,
hideIcon: true,
},
],
],
coverageDirectory : coverage ์ค์ ํ์ ๋ ์ ์ฅ๋๋ ํ์ผ์ ๊ฒฝ๋ก๋ฅผ ๋ํ๋
๋๋ค.collectCoverageFrom : coverage ์ ํฌํจ๋ / ์ ์ธ๋ ํ์ผ์ ์ค์ ํ ์ ์์ต๋๋ค.publicPath : report ๊ฐ ์ ์ฅ๋ ๊ฒฝ๋ก๋ฅผ ๋ํ๋
๋๋ค.filename : ์ ์ฅ๋ ํ์ผ์ ์ด๋ฆ์ ๋ํ๋
๋๋ค.pageTitle : ํ์ด์ง์ Title ์
๋๋ค.openReport : ํ
์คํธ ํ ์๋์ผ๋ก ํ์ผ์ด ์ด๋ฆฌ๋ ์ง ์ฌ๋ถ์
๋๋ค.inlineSource : ํ๋์ ํ์ผ๋ก ์ ์ฅํ๋์ง ์ฌ๋ถ์
๋๋ค.hideIcon : ์จ๊น ์์ด์ฝ์ด ์กด์ฌํ๋ ์ง ์ฌ๋ถ์
๋๋ค.๊ทธ ํ , ํ์์ฒ๋ผ ํ
์คํธ๋ฅผ ์งํํ๊ฒ ๋๋ฉด root ๊ฒฝ๋ก์ reports ํด๋๊ฐ ์๊ธฐ๊ณ , ์์ ํ์ผ์ด ์๊ธฐ๊ฒ ๋๋๋ฐ ๊ทธ ํ์ผ์ ์ด๋ฉด ์๋์ ๊ฐ์ ํ๋ฉด์ด ๋ฑ์ฅํฉ๋๋ค.


๋ง์ฝ ์ฌ๊ธฐ์ coverage ๊น์ง ํ์ธํ๊ณ ์ถ๋ค๋ฉด , package.json ์ script๋ก ์๋ ํญ๋ชฉ์ ์ถ๊ฐํฉ๋๋ค. ๋๋ ๋ฐ๋ก yarn test --coverage ๋ฅผ ์ฌ์ฉํด๋ ๊ด์ฐฎ์ต๋๋ค.
json
"coverage" : "jest --coverage"
์ดํ , yarn coverage ๋ฅผ ์คํํ๊ฒ ๋๋ฉด ์ฐ์ธก ์๋จ์ coverage ๋ฒํผ์ด ์ถ๊ฐ๋ ํ๋ฉด์ ๋ณผ ์ ์์ต๋๋ค. ํด๋น ๋ฒํผ์ ๋๋ฌ ๋ค์ด๊ฐ๋ฉด ํ
์คํธ๋ณ๋ก ํ๋ํ๋ ๋ณผ ์ ์์ต๋๋ค.


ํ
์คํธ๋์ง ์์ ๋ถ๋ถ์ ์ด๋ฐ ์์ผ๋ก ๋นจ๊ฐ ํ์ด๋ผ์ดํธ๊ฐ ์ถ๊ฐ๋ฉ๋๋ค. ์ด coverage๋ฅผ ๋ณด๊ณ ํ
์คํธ๊ฐ ๋์ง ์์ ๋ถ๋ถ์ ์ด๋์ธ์ง , ๋ ๋ถ๋ถ์ ์ด๋์ธ์ง ํ์ธ์ด ๊ฐ๋ฅํฉ๋๋ค.


ํ ์คํธ ์ถ๊ฐ ํ coverage ๋ฅผ ์ฑ์ฐ๊ณ ๋นจ๊ฐ ํ์ด๋ผ์ดํธ๊ฐ ์์ด์ง ๋ชจ์ต์ ๋๋ค. ๋ฌผ๋ก coverage ๋ฅผ ์ฑ์ฐ๋๋ฐ ๊ธ๊ธํ ํ ์คํธ ์ฝ๋๊ฐ ๋์ด์๋ ์๋๊ฒ ์ง๋ง , ์ ๋ฐ์ดํฐ๋ฅผ ํ์ธํด์ ํ ์คํธ ์ฝ๋์ ๋ํ ์ ๋ฐ์ ์ธ ์ ๊ฒ , ๋ถ์ ๋ฑ์ ์งํํ ์ ์์ด์ ํธ๋ฆฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ ๊ฒ ๊ฐ์ต๋๋ค.
ํ ์คํธ์ ๋ํด ๊ต์ฅํ ๋ง์ด ์๊ณ ์๋ค๊ณ ์๋ถํ๋๋ฐ , ์๊ฐ๋ณด๋ค ๊ฒํฅ๊ธฐ ๋๋์ผ๋ก ๋ง์ด ์๊ณ ์์๊ณ ์ ๋ชจ๋ฅด๊ณ ์ฌ์ฉํ ๋ถ๋ถ๋ ๋ง์ด ์์๋ ๊ฒ ๊ฐ์ต๋๋ค. ๋ํ ์ค๋ ์๊ฐ ์ฌ์ฉ๋๋ค๋ณด๋ ๊ด์ฑ์ ์ผ๋ก ์ฌ์ฉํ๊ณ ์์๋๋ฐ , ์๋ก ๋ณ๊ฒฝ๋ ๋ถ๋ถ๋ ๋ง์ด ์๊ฒ ๋์ด์ ์ข์๋ ๊ฒ ๊ฐ์ต๋๋ค. ์๋ง ๋ค์์๋ ์คํ ๋ฆฌ๋ถ ๊ด๋ จ ์์ ์ ์งํํ๋ฉด์ ์ถ๊ฐํ ์์ ์ธ๋ฐ , ๊ทธ ๋ component ํ ์คํธ๋ ํจ๊ป ์ถ๊ฐํ๋๋ก ํ๊ฒ ์ต๋๋ค.
์๋ชป๋ ๋ถ๋ถ์ด ์์ผ๋ฉด ์๋ ค์ฃผ์๋ฉด ๊ฐ์ฌ๋๋ฆฌ๊ฒ ์ต๋๋ค. ์ ์์ ์ด ์๋ repo ๋ ์ฌ๊ธฐ๋ฅผ ํ์ธํด์ฃผ์ธ์ !