ํ ์คํธ ๋ด์ฉ
ํ
์คํธ ํ๊ฒฝ jest | React Testing Library
React Router์ useNavigate๋ก ์ฐ๊ฒฐ๋์ด์๋ ํ์ด์ง๊ฐ ์๋ค.
ํ์ธ ๋ฒํผ์ ๋๋ฅด๋ฉด ์กฐ๊ฑด์ ์ถฉ์กฑํ๋์ง ๊ฒ์ฌ ํ ๋ค์ ํ์ด์ง๋ก ์ด๋ํ๋ ํ์ด์ง์ด๋ค.
์ด๋ฅผ ์๋์ ๊ฐ์ด ํ
์คํธํด์ผ ํ๋ค.
์ฝ๊ด๋์ ํ์ด์ง์์ ์ ์ฒด ๋์ ์ฒดํฌ ํ ํ์ธ๋ฒํผ์ ๋๋ฅด๋ฉด, ๋ค์ ํ์ด์ง์ธ ์ด๋ฆ ์
๋ ฅํ์ด์ง๋ก ์ด๋ํ๋ค.
ํ ์คํธํ๋ ค๋ ์ฝ๋
Agreement.tsx
export const Agreement = () => {
const navigate = useNavigate();
const [checked, setChecked] = useState(false);
const handleClick = () => {
if (checked) {
navigate('/name');
}
};
return (
<div>
์ฝ๊ด๋์ ํ์ด์ง
<input
type='checkbox'
id='all'
checked={checked}
onChange={() => setChecked(!checked)}
/>
<label htmlFor='all'>์ ์ฒด๋์</label>
<button onClick={handleClick}>ํ์ธ</button>
</div>
);
};
Name.tsx
export const Name = () => {
return <div>์ด๋ฆํ์ด์ง</div>;
};
App.tsx
React Router๊ฐ ๊ตฌํ๋์ด์๋ ๋ถ๋ถ
import { BrowserRouter, Route, Routes } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path='/agreement' element={<Agreement />} />
<Route path='/name' element={<Name />} />
</Routes>
</BrowserRouter>
);
}
export default App;
์ด์ ํ ์คํธ์ฝ๋
Agreement.test.tsx
์ฒ์์ ๋ผ์ฐํฐ์์ด ์๋์ ๊ฐ์ ์์ผ๋ก ๊ตฌํํ๋ค.
import { render } from '@testing-library/react';
describe('ํ์๊ฐ์
์ฝ๊ด๋์ ํ์ด์ง', () => {
it('์ ์ฒด ๋์ ์ฒดํฌ ํ ํ์ธ ๋ฒํผ ๋๋ฅด๋ฉด ๋ค์ ํ์ด์ง(์ด๋ฆ ์
๋ ฅ)๋ก ์ด๋ํ๋ค', async () => {
render(
<>
<Agreement />
<Name />
</>,
);
const checkbox = screen.getByRole('checkbox', { name: '์ ์ฒด๋์' });
expect(checkbox).toBeInTheDocument();
const confirmBtn = screen.getByRole('button', { name: 'ํ์ธ' });
expect(confirmBtn).toBeInTheDocument();
const user = userEvent.setup();
await user.click(checkbox);
expect(checkbox).toBeChecked();
await user.click(confirmBtn);
const titleOfNamePage = screen.getByText(/์ด๋ฆํ์ด์ง/);
expect(titleOfNamePage).toBeInTheDocument();
});
}
์ด๋ ๊ฒ ์์ฑํ์๋ ํ
์คํธ๊ฐ ๋ฐ๋ก ํต๊ณผํ๊ธฐ๋๋ฌธ์ ๋ณ ๋ฌธ์ ๊ฐ ์๋ ์ค ์์๋ค.
๊ทธ๋ฐ๋ฐ.. ์๊ณ ๋ณด๋ ๋ผ์ฐํฐ์ ๊ธฐ๋ฅ์ ์ ๋๋ก ํ
์คํธํ๊ฒ ์๋ ๊ฒ์ด์๋ค...
์ง๊ธ ์์ ๊ตฌํ ์ฝ๋์๋ ๋ณด๋ฉด React Router๋ก ํ์ด์ง์ ๋ผ์ฐํ
๊ฒฝ๋ก๋ฅผ ๋ถ์ฌํด์ฃผ๋ ์ญํ ์ App.tsx์์ ํด์ฃผ๊ณ ์๋๋ฐ, ํ
์คํธ์์๋ ์ด๋ ๊ฒ ์ ์๋ ๋ผ์ฐํ
๊ฒฝ๋ก๋ฅผ ๋ถ๋ฌ์จ ๊ณณ์ด ์๋ค. ํ
์คํธ๋ ๋จ์ง ์ฝ๊ด๋์ํ์ด์ง์ ์ด๋ฆํ์ด์ง ๋๊ฐ๋ฅผ ๋์์ ๋์์ ํด๋นํ๋ ์์๋ฅผ ์ฐพ์๊ธฐ ๋๋ฌธ์ ํต๊ณผํ ๊ฒ์ด์๋ค.
ํํ..
์ ๊ทธ๋ผ React Router๋ก ์ฐ๊ฒฐ๋๋ ํ์ด์ง๋ ์ด๋ป๊ฒ ํ
์คํธ ํ๋ ๊ฒ์ธ๊ฐ?
React Testing Library์ ๊ณต์ ๋ฌธ์์ React Router ๊ณต์๋ฌธ์๋ฅผ ๋ค์ ธ ํ
์คํธ ๋ ๋๋งํ๋ ํด๋น ํ์ด์ง์ <Routes> ์ ์๋ถ๋ฅผ `<BrowserRouter />` ํน์ `<MemoryRouter />`๋ก ๊ฐ์ธ์ฃผ์ด์ผ ํ๋ค๋ ๊ฒ์ ์์๋ค.
๊ทธ๋ฐ๋ฐ ๋ฌด์์ ์จ์ผํ๋ ๊ฒ์ธ์ง์ ๋ํ ๋ฉ๋ถ์ด ์ค๊ธฐ ์์..
BrowserRouter vs MemoryRouter
BrowserRouter๋ก ๊ตฌํ
์ฒ์์๋ RTL ๊ณต์๋ฌธ์์ ๋งจ ์ ์ฒซ์์ ์ ๋์ค๋ ๊ฒ์ฒ๋ผ BrowserRouter๋ก ๊ตฌํํด๋ณด์๋ค. ๊ทธ๋ฆฌ๊ณ ํ
์คํธ๊ฐ ์๋ ๋ผ์ฐํฐ๋ฅผ ์ฌ์ฉํ๋ ๊ตฌํ๋ถ์ธ App.tsx ์์ฒด๊ฐ BrowserRouter ๋ผ์ฐํฐ๋ก ๊ตฌํ๋์ด์์ผ๋๊น BrowserRouter๋ก ๊ตฌํํด์ผ๋๋ค๊ณ ์๊ฐํ๋ค;;
testUtils.tsx
import { RenderOptions, render } from '@testing-library/react';
interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
route?: string;
}
function renderWithBrowserRouter(
ui: React.ReactElement,
extendedRenderOptions: ExtendedRenderOptions = {},
) {
const {
route = '/',
} = extendedRenderOptions;
window.history.pushState({}, 'Test Page', route);
const Wrapper = ({ children }: PropsWithChildren) => (
<BrowserRouter>{children}</BrowserRouter>
);
return {
...render(ui, { wrapper: Wrapper }),
};
}
Agreement.test.tsx
import { renderWithBrowserRouter } from '@/utils/testUtils';
it(...
renderWithBrowserRouter(
<Routes>
<Route path='/agreement' element={<Agreement />} />
<Route path='/name' element={<Name />} />
</Routes>,
);
)
๊ทธ๋ฐ๋ฐ ํ
์คํธํ๋ ค๋ Element๋ฅผ ์ฐพ์ง ๋ชปํ๋ค๋ ์๋ฌ๊ฐ ๋ฐ์
TestingLibraryElementError: Unable to find an accessible element with the role "checkbox" and name "์ ์ฒด๋์"
BrowserRouter๋ง ๋์ฐ๋๊ฒ์ ํ
์คํธํ๋ ค๋ ์ปดํฌ๋ํธ๋ฅผ ๋์ฐ๋๊ฒ ์๋๊ณ ์ ์ํ ๊ฒฝ๋ก๋ฅผ ๋์ฐ๋ ๊ฒ์ด๋ค.
๊ทธ๋ฆฌ๊ณ renderWithBrowserRouter ํจ์์ ์ ์ํ route path๋ฅผ ๋ณด๋ฉด '/' ๋ก ์ ์ด์ฃผ์ด์ '/' ์ ํด๋นํ๋ ํ์ด์ง๋ฅผ ์ฒ์์ ๋ ๋๋ง ์ํค๋ ๊ฒ. root path์ธ `/` ๋ผ์ฐํฐ์ ํด๋นํ๋ ์ปดํฌ๋ํธ๊ฐ ์์ผ๋ฏ๋ก ํ
์คํธ ์ปดํฌ๋ํธ๊ฐ ๋ฐ์ผ์ด ์์๋ ๊ฒ.
MemoryRouter๋ก ๊ตฌํ
RTL ๊ณต์๋ฌธ์์ //use when you want to manually control the history ๋ผ๊ณ ์ ํ์๋ค.
๋ด๊ฐ ์๋์ผ๋ก ์ง์ ํ์คํ ๋ฆฌ๋ฅผ ์ ์ดํ๊ณ ์ถ์ผ๋ฉด MemoryRouter๋ฅผ ์จ๋ผ.
Agreement์์ Nameํ์ด์ง๋ก ๊ฐ๋ ๋ผ์ฐํ
์ ์ด๋ฏธ ์ปดํฌ๋ํธ์ ๋ด๋ถ์ ๊ตฌํ์ ํด๋์์ผ๋ ํ .. ์ง์ ์๋์ผ๋ก ํ๋๊ฑด ์๋๊ฑฐ๊ฐ์๋ฐ ํ๊ณ ํท๊ฐ๋ ธ๋๋ฐ, ์ด ๋ง์ ์ฆ ํ
์คํธ ํ๊ฒฝ์์๋ ์์ฒด์ ์ธ history stack์ ์ ์ดํ๊ณ ์ถ์ผ๋ฉด์ด๋ผ๋ ๋ป์ด์๋ค.
React Router ๊ณต์๋ฌธ์๋ฅผ ๋ณด๋ฉด ์ข ๋ ๋ช
ํํด์ง๋ค.
์๋ฅผ๋ค์ด `<BrowserRouter>`๋ ๋ธ๋ผ์ฐ์ ์ url์ ๋ธ๋ผ์ฐ์ ๋ด๋ถ์ ํ์คํ ๋ฆฌ ์คํ์ ์ ์ฅํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค. ๊ทธ๋ฐ๋ฐ ์ฐ๋ฆฌ ํ
์คํธ ํ๊ฒฝ์ ์ค์ ๋ธ๋ผ์ฐ์ ๋ก ํ๋๊ฒ ์๋๋ค. Node.js ํ๊ฒฝ์์ ๋ธ๋ผ์ฐ์ ์ DOM์ ์ ๊ทผํ๊ธฐ ์ํด์ ์ฌ์ฉํ๋ JSDOM ์ด๋ผ๋ ๋๊ตฌ๋ฅผ ํตํด ํ
์คํธ๋ฅผ ํ๋ ๊ฒ์ด๋ค. `<BrowserRouter>`๊ฐ ์ธ๋ถ ์์ค์ ์ฐ๊ฒฐ์ด ๋์ด์๊ธฐ ๋๋ฌธ์, ํ
์คํธํ ๋๋ history stack์ ์์ ํ ์ ์ดํ ์ ์๋ `<MemoryRouter>`๋ฅผ ์ฌ์ฉํ๋๊ฒ ์ด์์ ์ด๋ผ๊ณ ๊ณต์๋ฌธ์์์ ์ค๋ช
ํ๊ณ ์๋ค.
๋ฐ๋ผ์ ๋ฉ๋ชจ๋ฆฌ ๋ผ์ฐํฐ๋ก ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ์๋ค.
์์ ์ดํ ํ
์คํธ์ฝ๋
import { MemoryRouter, Route, Routes } from 'react-router-dom';
describe('ํ์๊ฐ์
์ฝ๊ด๋์ ํ์ด์ง', () => {
it('์ ์ฒด ๋์ ์ฒดํฌ ํ ํ์ธ ๋ฒํผ ๋๋ฅด๋ฉด ๋ค์ ํ์ด์ง(์ด๋ฆ ์
๋ ฅ)๋ก ์ด๋ํ๋ค', async () => {
render(
<MemoryRouter initialEntries={["/agreement", โ/nameโ]}>
<Routes>
<Route path="/agreement" element={<Agreement />} />
<Route path="/name" element={<Name />} />
</Routes>
</MemoryRouter>,
);
const checkbox = screen.getByRole('checkbox', { name: '์ ์ฒด๋์' });
expect(checkbox).toBeInTheDocument();
const confirmBtn = screen.getByRole('button', { name: 'ํ์ธ' });
expect(confirmBtn).toBeInTheDocument();
const user = userEvent.setup();
await user.click(checkbox);
expect(checkbox).toBeChecked();
await user.click(confirmBtn);
const titleOfNamePage = screen.getByText(/์ด๋ฆํ์ด์ง/);
expect(titleOfNamePage).toBeInTheDocument();
});
}
react-router-dom์์ ๊ฐ์ ธ์จ `<MemoryRouter>`์ปดํฌ๋ํธ์ initialEntries ์์ฑ์ ํ
์คํธํ๊ณ ์ถ์ ์ปดํฌ๋ํธ์ ์์๋๋ก ์ฐจ๋ก๋ก ์
๋ ฅํด์ค๋ค.
์ด๋ ๊ฒ ํ๋ฉด ๋ฉ๋ชจ๋ฆฌ๋ผ์ฐํฐ์์ ์ ์ด์ค ์ฐจ๋ก๋๋ก ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋๊ณ , ์์๋ค์ dom์์ ์ ์ฐพ์์ ํ
์คํธํ ํ ์ ์๋ค.
ํ
์คํธ๋ก ๋ฉ๋ถ์์ ๋์์ ๊ตฌํ๋ ์์๋ฆฌ ๋๋ฃ์์ ๋
ผ์ ํ ์๋์ฌ๋์ฑ.. ใ
ใ
์ค๋๋ง์ ๋๋ฃ๋ ์ด๋ฐ ์๊ธฐํ๋ ์ฌ๋ฏธ์์๋ค.. ๐คฃ
๊ฒฐ๋ก
๊ฒฐ๊ตญ `<MemoryRouter>`๋ ๋ฉ๋ชจ๋ฆฌ ํ์คํ ๋ฆฌ ์คํ์ผ๋ก url์ ๊ด๋ฆฌํ๋ฏ๋ก, ๋ด๋ถ์ ์ผ๋ก BrowserRouter๋ก ์ปดํฌ๋ํธ๊ฐ ๊ตฌํ์ด ๋์๋ค๊ณ ํด๋ ํ ์คํธํ๊ฒฝ์์๋ `<MemoryRouter>`๋ฅผ ์ฌ์ฉํ๋๊ฒ ์ธ๋ถ ์์ค์ ์์กดํ์ง ์๊ธฐ๋๋ฌธ์ ๋ ์ผ๊ด์ฑ์๊ฒ ์ ์ดํ๊ธฐ ์ฝ๋ค.