
React Test ์ฝ๋ ์์ฑ
๐ก ๊ฐ์ ๋ชจ๋ ๊ฐ๋ฐ์๋ค์ด ํ ์คํธ ์ฝ๋ ์์ฑ์ ์ค์์ฑ์ ๋ํด์ ์์ง๋ง, ์ฝ์ฌ๋ฆฌ ์ค๋ฌด์์ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด์ ๊ฐ๋ฐํ๊ธฐ๋ ์ฌ์ด ์ผ์ด ์๋๋ผ๋ ๊ฒ์ ๊ณต๊ฐํ์ค ๊ฒ์ ๋๋ค. ํนํ๋ ํ๋ก ํธ์๋์์๋ ์๊ฐ์ ์ธ ๋ถ๋ถ์ด ๋ง๋ค ๋ณด๋ ์๋ฌด๋๋ ๋์ผ๋ก ์ง์ ๊ฒ์ฆํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ฐ์. ํ ์คํธ ์ฝ๋๋ฅผ ํตํดโฆ
1. ๊ธฐ์ ์คํ
- react 18
- react-router-dom v6
- react-query
- testing-library
๊ธฐ์ ์คํ์ ๊ฒฝ์ฐ React 18๊ณผ React-router-dom v6๊ณผ ๊ฐ์ด ์ต์ ๋ฒ์ ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์๊ณ , API ํธ์ถ์ ์ํ Axios์ ์๋ฒ ์ํ๊ด๋ฆฌ๋ฅผ ์ํ React-query๋ฅผ ์ด์ฉํ์์ต๋๋ค. React ๊ณต์ ํํ์ด์ง์๋ ๊ฒ์๋์ด ์๋ฏ Facebook์์๋ React Testing Library ์ฌ์ฉ์ ๊ถ์ฅํ๋ ๋งํผ, ํ
์คํธ ์ฝ๋ ์์ฑ์ Testing-library๋ฅผ ์ด์ฉํ์ฌ ์งํํ์์ต๋๋ค.
2. React Testing Library
React Testing Library(RTL)๋ ๊ธฐ์กด์ Enzyme์ ๊ฐ์ ๋๊ตฌ์ ๊ตฌํ ๊ธฐ๋ฐ์ ํ
์คํธ ๋ฐฉ๋ฒ๋ก ์ ๋จ์ ์ ๋ณด์ํ๊ธฐ ์ํด ๋ฑ์ฅํ์๊ณ , ํ์ ์ฃผ๋ ํ
์คํธ ๋ฐฉ๋ฒ๋ก ์ผ๋ก ํ
์คํธ๋ฅผ ์งํํฉ๋๋ค.
๋จผ์ , ๊ตฌํ ๊ธฐ๋ฐ์ ํ
์คํธ ๋ฐฉ๋ฒ๋ก ์ด๋, ์ฃผ๋ก ์ ํ๋ฆฌ์ผ์ด์
์ด ์ด๋ป๊ฒ ๋์ํ๋์ง์ ๋ํด์ ์ด์ ์ ๋์ด ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค.
<div class="title">Hello</div>์๋ฅผ ๋ค์ด ์์ ๊ฐ์ ์ฝ๋๊ฐ ์์ ๊ฒฝ์ฐ, ์ปดํฌ๋ํธ์ ์ด๋ค prop์ด ๋์ด๊ฐ๊ณ ํ์ฌ state๊ฐ ์ด๋ป๊ฒ ๋๋์ง, div ํ๊ทธ๊ฐ ์ฐ์๊ณ title์ด๋ผ๋ ํด๋์ค๊ฐ ์ฌ์ฉ๋์๋์ง ๋ฑ์ ํ
์คํธํฉ๋๋ค.
์ด์ ๋ฐํด ํ์ ์ฃผ๋ ํ
์คํธ ๋ฐฉ๋ฒ๋ก ์ ๊ฒฝ์ฐ, ์ธ๋ถ์ ์ธ ๊ตฌํ ์ฌํญ๋ณด๋ค๋ ์ค์ ์ฌ์ฉ์ ๊ฒฝํ๊ณผ ์ ์ฌํ ๋ฐฉ์์ผ๋ก ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค. ์ฆ, ์ฌ์ฉ์ ์
์ฅ์์๋ div ํ๊ทธ๊ฐ ์ฐ์๋์ง, title์ด๋ผ๋ ํด๋์ค๊ฐ ์ฌ์ฉ๋์๋์ง๋ณด๋ค๋ ํ๋ฉด์ ์ฌ๋ฐ๋ฅธ ์ฝํ
์ธ ๋ด์ฉ์ด ํ๊ธฐ๋๋๊ฐ๋ผ๋ ๋ถ๋ถ์ด ๋ ์ค์ํ๊ธฐ ๋๋ฌธ์, RTL์ Hello๋ผ๋ ํ
์คํธ๊ฐ ํ๋ฉด์ ์๋์ง๋ฅผ ํ
์คํธํฉ๋๋ค. ๋ฐ๋ผ์ ์ด์ ๊ฐ์ด ๊ตฌํ๋ณด๋ค ๊ธฐ๋ฅ์ ์ด์ ์ ๋ง์ถ ํ
์คํธ ๋ฐฉ์์ ์ ๋ขฐ๋๋ฅผ ๋์๊ณผ ๋์์ ์ฝ๋ ๋ฆฌํฉํ ๋ง ์ ํ
์คํธ ์ฝ๋ ์์ ๋น๋๋ฅผ ์ค์ผ ์ ์์ต๋๋ค.
3. ์ปดํฌ๋ํธ ๋ ๋๋ง ํ ์คํธ
๋จผ์ , ๊ฐ๋จํ ์์๋ฅผ ํตํด RTL์ ํ์ฉํด์ ์ด๋ป๊ฒ ํ ์คํธํ๋์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค.
const Home = () => {
return (
<section>
<h1>Hello Word</h1>
<p>์๋
ํ์ธ์. ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ MAENG์
๋๋ค.</p>
</section>
)
}์์ ๊ฐ์ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ง๊ณ ํ
์คํธ๋ฅผ ์งํํ ๊ฒฝ์ฐ, ์ฌ์ฉ์ ์
์ฅ์์ ์๊ฐํด๋ณด๋ฉด ๋จผ์ Hello world๋ผ๋ ๋ฌธ๊ตฌ์ ์๋
ํ์ธ์. ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ MAENG์
๋๋ค.๋ผ๋ ๋ฌธ๊ตฌ๊ฐ ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋์์ ๋ ํ๋ฉด์ ๋
ธ์ถ๋์ด์ผ ํ ๊ฒ์
๋๋ค.
RTL์ ๊ฐ์ง๊ณ ํด๋น ๋ด์ฉ๋ค์ด ์ ๋๋ก ๋ ธ์ถ๋๋์ง ํ ์คํธํ๊ธฐ ์ํด์๋ ๋ค์๊ณผ ๊ฐ์ด ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
describe('Home', () => {
it('renders Home', () => {
const { getByText } = render(<Home />)
expect(getByText('Hello world')).toBeInTheDocument()
expect(getByText('์๋
ํ์ธ์. ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ MAENG์
๋๋ค.')).toBeInTheDocument()
})
})4. ์ ์ ์ธํฐ๋์ ํ ์คํธ
์ด์ ๋ฒํผ์ด๋ ์
๋ ฅ ํ๋ ๋ฑ ์ ์ ์ธํฐ๋์
์ด ์ด๋ฃจ์ด์ง๋ ๋ถ๋ถ์ ์ด๋ป๊ฒ ํ
์คํธํ๋์ง ์ดํด๋ณผ ์ฐจ๋ก์
๋๋ค.
const Button = ({ onClick }) => {
return <button onClick={onClick}>๋ฒํผ</button>
}์์ ๊ฐ์ ๋ฒํผ ์ปดํฌ๋ํธ๊ฐ ์์ ๊ฒฝ์ฐ, ์ฌ์ฉ์ ์
์ฅ์์ ์๊ฐํด๋ณผ ๋ "๋ฒํผ"์ด๋ผ๋ ํ
์คํธ๋ฅผ ๊ฐ์ง ๋ฒํผ์ ํด๋ฆญํ๋ฉด onClick ํจ์๊ฐ 1๋ฒ ์คํ๋ ๊ฒ์ ๊ธฐ๋ํ ์ ์์ต๋๋ค.
์์ ๋ด์ฉ์ ํ ์คํธ ์ฝ๋๋ก ์์ฑํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
describe('Button', () => {
it('renders Button', () => {
const onClick = jest.fn() // ๊ฐ์ง ํจ์
const { getByText } = render(<Button onClick={onClick} />)
expect(getByText('๋ฒํผ')).toBeInTheDocument()
fireEvent.click(button)
expect(onClick).toBeCalledTimes(1)
})
})์ ์ฝ๋๋ฅผ ๋ณด๋ฉด jest.fn()์ ์ฌ์ฉํ์ฌ onClick ํจ์๋ฅผ ๋ชจํนํ๋๋ฐ, ๋ชจํน์ ๋ํด์๋ ํ๋จ์ ๋ฐ๋ก ์ ๋ฆฌํ์ฌ ์ค๋ช
ํ๋๋ก ํ๊ฒ ์ต๋๋ค.
์ด๋ฒ์๋ ์ ๋ ฅ ํ๋์ ๊ฒฝ์ฐ ์ด๋ป๊ฒ ํ ์คํธ๋ฅผ ์งํํ๋์ง ์ดํด๋ณด๊ฒ ์ต๋๋ค.
const Form = () => {
const [text, setText] = useState('')
return (
<form>
<input
type="text"
placeholder="ํ
์คํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์."
value={text}
onChange={({ target: { value } }) => setText(value)}
/>
<button disabled={!text}>์ ์ถ</button>
</form>
)
}์์ ๊ฐ์ ํผ ์ปดํฌ๋ํธ๊ฐ ์์ ๊ฒฝ์ฐ, ๋ง์ฐฌ๊ฐ์ง๋ก ์ฌ์ฉ์ ์
์ฅ์์ ์๊ฐํด๋ณด๋ฉด "ํ
์คํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์."๋ผ๋ placeholder๊ฐ ์๋ ์
๋ ฅ ํ๋์ ํ
์คํธ๋ฅผ ์
๋ ฅํ๋ฉด ํด๋น ์
๋ ฅ ํ๋์ ๊ฐ์ ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ๊ฐ์ผ๋ก ๋ณํ ๊ฒ์ด๊ณ , "์ ์ถ"์ด๋ผ๋ ํ
์คํธ๋ฅผ ์ง๋ ๋ฒํผ์ด ํ์ฑํ๋ ๊ฒ์
๋๋ค.
์์ ๋ด์ฉ์ ํ ์คํธ ์ฝ๋๋ก ์์ฑํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
describe('Form', () => {
it('renders Form', () => {
const { getByText, getByPlaceholderText } = render(<Form />)
const input = getByPlaceholderText('ํ
์คํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.')
const button = getByText('์ ์ถ')
expect(button).toBeDisabled()
fireEvent.change(input, { target: { value: 'Maeng Input Test' } })
expect(input.value).toBe('Maeng Input Test')
expect(button).toBeEnabled()
})
})5. Custom Hook ํ ์คํธ
์์ฆ React์์๋ ํจ์ํ ์ปดํฌ๋ํธ์ ์ฌ๋ฌ ๊ฐ์ง ํ ์ ์ด์ฉํด์ ๊ฐ๋จํ๋ฉด์๋ ์ฌ์ฌ์ฉ์ฑ์ ๋์ธ ๋ฐฉ์์ผ๋ก ํ๋ก๊ทธ๋๋ฐ์ ํ๊ธฐ์, ์ฌ๋ฌ ์ปดํฌ๋ํธ์์ ๋ฐ๋ณต์ ์ผ๋ก ์ฌ์ฉ๋๊ฑฐ๋ ๋ณต์กํ ๊ณ์ฐ์ด ํฌํจ๋ ํ ๋ค์ ๋ณ๋์ ์ปค์คํ ํ ์ผ๋ก ๋ง๋ค์ด ๊ด๋ฆฌํ๋ ๊ฒ์ด ์ฉ์ดํฉ๋๋ค. ๋ฐ๋ผ์ React๋ฅผ ํ ์คํธํ ๋ ๋จ์ ์ปดํฌ๋ํธ ๋ ๋๋ง๋ฟ ์๋๋ผ ์ด๋ฌํ ํ ๋ค์ ๋ํ ํ ์คํธ๋ ์ด๋ฃจ์ด์ ธ์ผ ํฉ๋๋ค.
๋ฆฌ์กํธ ์ปค์คํ ํ ์ ํ ์คํธํ๊ธฐ ์ํด์ ๋ค์๊ณผ ๊ฐ์ด ์ฌ๋ฌ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด Hook ํ ์คํธ
- Custom Hook์ ์ฌ์ฉํ๋ ํ ์คํธ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด ํ ์คํธ
@testing-library/react-hooks์renderHook์ ์ฌ์ฉ
์ ๋ ์ด ์ค์์ @testing-library/react-hooks๋ฅผ ์ด์ฉํด์ ์ปค์คํ
ํ
์ ํ
์คํธํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์ค๋ช
ํ๊ฒ ์ต๋๋ค.
@testing-library/react-hooks ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํ๋ฉด ๋ณ๋์ React Component์ ๋์ ์์ด๋ React Hook์ ์ง์ ํ
์คํธํ ์ ์์ต๋๋ค.
์ง์ ์ฝ๋๋ฅผ ์์ฑํ์ฌ ์ค๋ช ๋๋ฆฌ๋๋ก ํ๊ฒ ์ต๋๋ค.
const useInput = (initialValue = null) => {
const [value, setValue] = useState(initialValue)
const handler = useCallback((e) => {
setValue(e.target.value)
}, [])
return [value, handler]
}์์ useInput์ด๋ผ๋ ์ปค์คํ
ํ
์ ์
๋ ฅ ๊ฐ์ ๋ด์ value ์ํ๊ฐ๊ณผ ๊ทธ ์ํ๊ฐ์ ์์ ํ ์ ์๋ handler๋ฅผ ๋ฐฐ์ด์ ๋ด์ ๋ฐํํฉ๋๋ค. ์ด๊ธฐ ์ํ๊ฐ์ initialValue ์ธ์๋ฅผ ํตํด ๋ฐ์ ์ ์์ผ๋ฉฐ, ์ธ์๋ฅผ ๋๊ธฐ์ง ์์ ๊ฒฝ์ฐ ๊ธฐ๋ณธ๊ฐ์ผ๋ก null์ ์ฌ์ฉํฉ๋๋ค.
Custom Hook ํ ์คํธ์ ๊ฒฝ์ฐ, ์ปดํฌ๋ํธ์ฒ๋ผ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ง๋ ๋ถ๋ถ์ด ์๋๋ค ๋ณด๋ ์ปดํฌ๋ํธ ํ ์คํธ์๋ ์กฐ๊ธ ๋ค๋ฅด๊ฒ, ์ฌ์ฉ์ ์ ์ฅ์ด ์๋ Jest์์ ๊ธฐ๋ฅ ํ ์คํธ๋ฅผ ์งํํ๋ฏ ๋ ๋๋ง์ด ์๋ ํจ์์ฒ๋ผ ๋งค๊ฐ๋ณ์์ ๋ฐ๋ฅธ ๊ฒฐ๊ณผ๊ฐ, ์ฆ ๋์ผํ ๋งค๊ฐ๋ณ์์ ๋ํด์ ๋์ผํ ๊ฒฐ๊ณผ๊ฐ์ ๋์ถํ๋์ง๋ฅผ ํ ์คํธํฉ๋๋ค.
๋ฐ๋ผ์ useInput ํ ์ ์ธ์ ์์ด ํธ์ถํ์ ๋ value๋ null์ธ ์ํ๊ฐ์ ์ง๋๊ณ , handler ํจ์๋ฅผ ํธ์ถํ ๊ฒฝ์ฐ value์ ์ํ๊ฐ์ handler์ ์ธ์๋ก ๋ค์ด์จ ๊ฐ์ ์ํด ๋ณํ๊ฐ ์ด๋ฃจ์ด์ง ๊ฒ์ด๋ผ๊ณ ๊ธฐ๋ํ ์ ์์ต๋๋ค. ์ด๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ํ ์คํธ ์ฝ๋๋ก ์์ฑํ ์ ์์ต๋๋ค.
describe('useInput', () => {
it('update value when handler is called', () => {
const { result } = renderHook(() => useInput())
expect(result.current[0]).toBe(null)
act(() => result.current[1]({ target: { value: 'Maeng Input Test' } }))
expect(result.current[0]).toBe('Maeng Input Test')
})
})renderHook() ํจ์์ React Hook ํจ์๋ฅผ ํธ์ถํ๋ ์ฝ๋๋ฅผ ์ธ์๋ก ๋๊ธฐ๋ฉด result ์์ฑ์ ๋ด๊ณ ์๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค. ์ด result ๊ฐ์ฒด๋ current ์์ฑ์ ๊ฐ๋๋ฐ, ์ด ์์ฑ์ ํตํด์ ํด๋น React Hook ํจ์์ ๋ฐํ๊ฐ์ ์ง์ ์ ๊ทผํ ์ ์์ต๋๋ค.
6. API ํธ์ถ ํ ์คํธ
์ด์ API ํธ์ถ์ ๊ฒฝ์ฐ ์ด๋ป๊ฒ ํ ์คํธํ๋์ง ์์๋ณด๊ฒ ์ต๋๋ค.
API ํธ์ถ ํ ์คํธ์ ๊ฒฝ์ฐ๋ Custom Hook ํ ์คํธ์ ๋น์ทํ๊ฒ, ํด๋น ํ ์ ํธ์ถํ์์ ๋ ์๋ฒ๋ก API ์์ฒญ์ ๋ณด๋ด๊ณ ์ฌ๋ฐ๋ฅธ ์๋ต์ ๋ฐ๋์ง ํ ์คํธ๋ฅผ ์งํํ๋ฉด ๋ฉ๋๋ค.
์ ์ ๊ฒฝ์ฐ, API ํธ์ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก Axios๋ฅผ ์ฌ์ฉํ์๊ณ , ์๋ฒ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก React-query๋ฅผ ์ด์ฉํ์์ต๋๋ค. ํด๋น ํฌ์คํธ๋ ํ
์คํธ ์ฝ๋ ์์ฑ์ ๋ํ ๊ธ์ด๋ฏ๋ก Axios๋ React-query์ ๋ํ ์ค๋ช
์ ์๋ตํ๋๋ก ํ๊ฒ ์ต๋๋ค.
๋จผ์ , React-query๋ฅผ ์ด์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๊ฐ๋จํ ์ปค์คํ ํ ์ ์์ฑํด๋ณด๊ฒ ์ต๋๋ค.
const useFetchData = () => {
const { data, isLoading, error } = useQuery('/data', async () => {
const response = await API.get(`/data`)
return response.data
})
return { data, isLoading, error }
}useFetchData ํ
์ API๋ฅผ ์์ฒญํ๊ณ ์๋ต๊ฐ์ data์ ๋ด์ { data, isLoading, error } ๊ฐ์ฒด๋ฅผ ๋ฆฌํดํฉ๋๋ค.
๋ฐ๋ผ์ useFetchData ํ ์ ํธ์ถํ์ ๋ ๋ค์๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ํ ์ ์์ต๋๋ค.
// ๋ฐ์ดํฐ ์์ฒญ ์๋ฃ ์
{
data: undefined,
isLoading: true,
error: null
}
// ์ ์์ ์ผ๋ก ๋ฐ์ดํฐ ์์ฒญ ์๋ฃ
{
data: { name: 'Maeng' },
isLoading: false,
error: null
}์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
describe('useFetchData', () => {
it('fetch data', async () => {
const { result, waitFor } = renderHook(() => useFetchData(), {
wrapper: createQueryWrapper(),
})
await waitFor(() => !result.current.isLoading)
expect(result.current.data?.name).not.toBe(undefined)
})
})React-query๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ๋ณ๋์ Wrapper ์ปดํฌ๋ํธ๊ฐ ํ์ํฉ๋๋ค. ๋ฐ๋ผ์ createQueryWrapper๋ผ๋ ํจ์๋ฅผ ๋ง๋ค์ด React-query๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก Wrapper ์ปดํฌ๋ํธ๋ฅผ ์์ฑํด์ฃผ์์ต๋๋ค.
// React-query๋ฅผ ์ํ Wrapper ์ปดํฌ๋ํธ ์์ฑ
const createQueryWrapper = () => {
const testQueryClient = createTestQueryClient()
const Wrapper = ({ children }: { children }) => (
<QueryClientProvider client={testQueryClient}>{children}</QueryClientProvider>
)
return Wrapper
}ํ์ง๋ง ์์ฒ๋ผ API ํธ์ถ ํ ์คํธ๋ฅผ ์งํํ๋ฉด์ ๋ช ๊ฐ์ง ๋ถํธ์ฌํญ์ ๊ฒช๊ฒ ๋์์ต๋๋ค.
์ฒซ ๋ฒ์งธ๋ API์ ์๋ต์ ๋ด๋ ค์ฃผ๋ ์๋ฒ๊ฐ ์ ๋๋ก ๋์ํ์ง ์์ ๊ฒฝ์ฐ ๋น์ฐํ๊ฒ๋ ์คํจ ๋ฉ์์ง๋ฅผ ๋ ธ์ถํ๊ฒ ๋ฉ๋๋ค. ๋ฐฑ์๋ ๊ตฌํ์ด ์์ง ์ ๋ ์ํ์์ ํ๋ก ํธ ์์ ์ ํ๋ค๋ฉด ๊ณ์ํด์ ํ ์คํธ ์ผ์ด์ค์์ ์คํจ๊ฐ ๋ฐ์ํ ๊ฒ์ ๋๋ค.
๋ ๋ฒ์งธ๋ API๊ฐ DB์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ ์์์๋ ๋จ์ํ data๊ฐ undefined๊ฐ ์๋์ ํ
์คํธํ์์ง๋ง, ์ข ๋ ์์ธํ๊ฒ data ๊ฐ์ ๋ํด ํ
์คํธ๋ฅผ ์งํํ ๊ฒฝ์ฐ DB๊ฐ ์์ ๋จ์ ๋ฐ๋ผ ์๋ต๊ฐ์ด ๋งค๋ฒ ๋ฐ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์ด ๋ํ ํ
์คํธ ์ผ์ด์ค์์ ์คํจ๊ฐ ๋ฐ์ํ ๊ฒ์
๋๋ค.
์ธ ๋ฒ์งธ๋ CORS ์ฒ๋ฆฌ๋ฅผ ์ถ๊ฐํด์ฃผ์ด์ผ ํ๋ ๋ฒ๊ฑฐ๋ก์์ด ์์์ต๋๋ค. ์ ๊ฐ ๊ตฌํํ ์๋ฒ์ ๊ฒฝ์ฐ Access-Control-Allow-Origin์, React์ ๊ฒฝ์ฐ ํฌํธ ๋ฒํธ๋ก 3000๋ฒ์ ์ฌ์ฉํ๊ธฐ์ 3000๋ฒ๋ง ํ์ฉํด์ฃผ์๋๋ CORS ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค. ํ์ธํด ๋ณธ ๊ฒฐ๊ณผ ํ
์คํธ ์ฝ๋์ ๊ฒฝ์ฐ API ํธ์ถ ์ 80 ํฌํธ๋ฅผ ์ด์ฉํด์ ์์ฒญํ๊ณ ์์ด์, ๋ง์ฝ API ์๋ฒ์์ 3000๋ฒ ํฌํธ๋ง ํ์ฉํ๊ณ ์๋ค๋ฉด 80 ํฌํธ๋ ํ์ฉํ๋๋ก ์ถ๊ฐ์ ์ธ ์์
์ด ํ์ํ์ต๋๋ค.
๋ฐ๋ผ์ ์ด๋ป๊ฒ ํด๊ฒฐํ๋ฉด ์ข์๊น ๊ณ ๋ฏผํ๋ ์ค axios-mock-adapter์ ๋ํด ์๊ฒ ๋์์ต๋๋ค.
axios-mock-adapter๋ฅผ ํ์ฉํ๋ฉด API ์์ฒญ๊ณผ ์๋ต์ Mockingํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ API ์์ฒญ๊ณผ ์๋ต๊ฐ์ ํ
์คํธ ์ฝ๋ ๋ด์์ ์กฐ์ํ ์ ์์ต๋๋ค.
๋ค์์ axios-mock-adapter๋ฅผ ํ์ฉํด์ ํ
์คํธ ์ฝ๋๋ฅผ ์ฌ์์ฑํด๋ณธ ๊ฒฐ๊ณผ์
๋๋ค.
describe('useFetchData', () => {
it('fetch data', async () => {
mock.onGet('/data').reply(200, { name: 'Maeng' })
const { result, waitFor } = renderHook(() => useGetBankList(), {
wrapper: createQueryWrapper(),
})
await waitFor(() => !result.current.isLoading)
expect(result.current.data.name).toBe('Maeng')
})
})์ด์ฒ๋ผ axios-mock-adapter๋ฅผ ํ์ฉํ๋ฉด ์ง์ API ์์ฒญ์ ๋ณด๋ด์ง ์๊ณ ๋ ์์๋๋ ์๋ต ์ํ์ ๊ฐ์ ๋ด๋ ค์ฃผ๋๋ก API๋ฅผ ๋ชจํนํ์ฌ ํ
์คํธ๋ฅผ ์งํํ ์ ์์ต๋๋ค.
7. ๋ผ์ฐํ ํ ์คํธ
๋ค์์ผ๋ก, ์ปดํฌ๋ํธ ๋๋ ๋ฆฌ์กํธ ํ ์์ ๋ฐ์ํ๋ ๋ผ์ฐํ ์ ์ด๋ป๊ฒ ํ ์คํธํ๋์ง ์์๋ณด๊ฒ ์ต๋๋ค. ๋๊ฐ React์์๋ React-router-dom ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํด์ ๋ผ์ฐํ ์ค์ ์ ํ๋๋ฐ์. ์ต๊ทผ์ v6๊ฐ ๋ฆด๋ฆฌ์ฆ๋์ด ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ์์ต๋๋ค.
๋จผ์ , ๋ค์๊ณผ ๊ฐ์ด ๋ผ์ฐํ ์ด ๋ฐ์ํ๋ ๊ฐ๋จํ ์ปดํฌ๋ํธ๋ฅผ ์์ฑํด๋ณด๊ฒ ์ต๋๋ค.
const Button = () => {
const navigate = useNavigate()
const onClick = () => {
navigate('/')
}
return <button onClick={onClick}>๋ฒํผ</button>
}์, ๋ผ์ฐํ
์ด ๋ฐ์ํ๋ ์ปดํฌ๋ํธ์
๋๋ค. ์์ ๋ง์๋๋ ธ๋ค์ํผ ์ปดํฌ๋ํธ ํ
์คํธ๋ ์ฌ์ฉ์ ์
์ฅ์์ ์๊ฐํ์ฌ ํ
์คํธ๋ฅผ ์งํํฉ๋๋ค. ๋ฐ๋ผ์ ์ด ์ปดํฌ๋ํธ์์ ์ฐ๋ฆฌ๊ฐ ๊ธฐ๋ํ๋ ์๋๋ฆฌ์ค๋ "๋ฒํผ"์ด๋ผ๋ ํ
์คํธ๋ฅผ ์ง๋ ๋ฒํผ์ ํด๋ฆญํ๋ฉด "/"๋ก ๋ผ์ฐํ
๋๋ ๊ฒ์
๋๋ค.
์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ์ ์์ react-router-dom์ ๋์์ ๋ํด์ ์๊ฐํด ๋ณด์์ผ ํฉ๋๋ค. ์กฐ๊ธ ์ ์ react-query ํ ์คํธ๋ฅผ ํ ๋ Wrapper ์ปดํฌ๋ํธ๊ฐ ํ์ํ์๋ฏ์ด, react-router-dom์ด ๋์ํ๊ธฐ ์ํด์๋ Wrapper ์ปดํฌ๋ํธ๊ฐ ํ์ํฉ๋๋ค.
๋ฐ๋ผ์ React-router-dom์ ์ํด ํ
์คํธ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ ๋ ๋๋งํด์ค ์ ์๋ renderWithRouter ํจ์๋ฅผ ์์ฑํด๋ณด๊ฒ ์ต๋๋ค.
// React-router-dom์ ์ํ Wrapping
const renderWithRouter = (children) => {
return {
...render(<MemoryRouter>{children}</MemoryRouter>
)
};๋ณดํต React์์๋ React-router-dom์์ ์ ๊ณตํ๋ BrowserRouter ๋๋ HashRouter๋ฅผ ํตํด ์ปดํฌ๋ํธ๋ค์ ๊ฐ์ธ ๋ผ์ฐํ
์ ๊ตฌํํ๋๋ฐ, MemoryRouter๋ <BrowserHistory> ๋ฐ <HashHistory>์๋ ๋ฌ๋ฆฌ ๋ธ๋ผ์ฐ์ ์ ๊ธฐ๋ก ์คํ๊ณผ ๊ฐ์ ์ธ๋ถ ์์ค์ ์ฐ๊ฒฐ๋์ง ์์ต๋๋ค. ๋ฐ๋ผ์ ํ
์คํธ์ ๊ฐ์ด ๊ธฐ๋ก ์คํ์ ์์ ํ ์ ์ดํด์ผ ํ๋ ์๋๋ฆฌ์ค์ ์ด์์ ์ด๋ผ๊ณ ํฉ๋๋ค.
์ด์ ๋ผ์ฐํ ์ ํ ์คํธํ๊ธฐ ์ํ ์ค๋น๋ฅผ ๋ง์ณค์ผ๋ ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํด์ ํ ์คํธ๋ฅผ ์งํํ์์ต๋๋ค.
// useNavigate ๋ชจํน
const mockedNavigator = jest.fn()
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedNavigator,
}))
describe('Button', () => {
it('renders Button', () => {
const { getByText } = renderWithRouter(<Button />)
expect(getByText('๋ฒํผ')).toBeInTheDocument()
fireEvent.click(button)
expect(mockedNavigator).toBeCalledTimes(1)
})
})ํด๋น ํ ์คํธ ์ฝ๋๋ฅผ ์คํ์ํค๋ฉด ์ ์์ ์ผ๋ก ํต๊ณผํ๋ ๊ฒ์ ํ์ธํ ์ ์์ง๋ง, ์ ๋ ์์ง ๋ง์กฑํ์ง ๋ชปํ์ต๋๋ค.
์ ํํ "/"๋ก ๋ผ์ฐํ ๋๋์ง๊น์ง ํ ์คํธ๋ฅผ ์งํํ์ฌ ์ข ๋ ์๋ฒฝํ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์ถ์์ต๋๋ค.
ํ์ง๋ง ๊ตฌ์ฑ ์์๊ฐ ์ผ๋ถ ์์
์์ ๊ฒฝ๋ก๋ฅผ ๋ณ๊ฒฝํ๋ ๊ฒฝ์ฐ MemoryRouter๋ฅผ ์ฌ์ฉํ์ฌ๋ history์ ๋ณ๊ฒฝ ์ฌํญ์ ์ก์ธ์คํ ์ ์๊ธฐ ๋๋ฌธ์ MemoryRouter๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ง์ผ๋ก๋ ์ถฉ๋ถํ์ง ์๋ค๋ ์ฌ์ค์ ์๊ฒ ๋์๊ณ , history์ ์ก์ธ์คํ ์ ์๋ HistoryRouter๋ฅผ ํ์ฉํ๊ณ ์ ํ์์ต๋๋ค.
๋ค์์ HistoryRouter๋ฅผ ํ์ฉํด์ renderWithRouter๋ฅผ ์ฌ๊ตฌํํ ์ฝ๋์
๋๋ค.
const renderWithRouter = (renderComponent, route) => {
const history = createMemoryHistory();
if (route) {
history.push(route);
}
return {
...render(
<HistoryRouter history={history}>
<Routes>{renderComponent()}</Routes>
</HistoryRouter>
),
history,
};์ด์ ๋ history์ ๋ณ๊ฒฝ ์ฌํญ์ ๋ํด ์ก์ธ์คํ ์ ์์ต๋๋ค.
๋ค์ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด๋ณด๊ฒ ์ต๋๋ค.
describe('Button', () => {
it('renders Button', () => {
const { getByText, history } = renderWithRouter(
() => (
<>
<Route path="/test" element={<Button />} />
<Route path="/" element={<div />} />
</>
),
'/test',
)
expect(getByText('๋ฒํผ')).toBeInTheDocument()
fireEvent.click(button)
await waitFor(() => {
expect(history.location.pathname).toEqual('/')
})
})
})์ด์ ์ฝ๋์ ๋ฌ๋ฆฌ useNavigate๋ฅผ ๋ชจํนํ์ง ์์๋ ๋๋ฉฐ, history ๊ฐ์ฒด์ ์ก์ธ์คํ์ฌ ์ ํํ๊ฒ "/"๋ก ๋ผ์ฐํ ๋๋์ง ํ ์คํธํ ์ ์๊ฒ ๋์์ต๋๋ค.
8. ์ฌํ ํ ์คํธ
์ด์ ๋ ์์์ ํ์ตํ ๋ด์ฉ์ ๋ฐํ์ผ๋ก ๋ณตํฉ์ ์ธ ํ ์คํธ๋ฅผ ์งํํด๋ณด๊ฒ ์ต๋๋ค.
๋ค์์ ์๋ฒ๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ด๋ฅผ ์ ๋ฐ์ดํธํ๋ ํ์ด์ง ์ปดํฌ๋ํธ์ ๋๋ค. ์ปดํฌ๋ํธ ์์ธ ์ฝ๋๋ ์๋ตํ๊ณ ํ ์คํธ ์ฝ๋ ์์ฃผ๋ก ์์ฑํ๊ณ ์ค๋ช ๋๋ฆฌ๊ฒ ์ต๋๋ค.
const UpdatePage = () => {
const { id } = useParams()
const navigate = useNavigate()
const { data, error, isLoading, isError } = useFetchData(id)
const { mutateAsync, isLoading: isMutating } = useUpdateData()
const onFormSubmit = async (formData) => {
await mutateAsync({ ...formData, id })
navigate('/')
}
if (isLoading) {
return <div data-testid="loader">Loading</div>
}
if (isError) {
return <div>Error: {error.message}</div>
}
return (
<div>
<h1>Update Data</h1>
<DataForm defaultValues={data} onFormSubmit={onFormSubmit} isLoading={isMutating} />
</div>
)
}์ ๋ฐ์ดํธ ํ์ด์ง์ ๋ํด์ ๊ฐ๋ตํ๊ฒ ์ค๋ช ๋๋ฆฌ์๋ฉด, ํ์ฌ url๋ก๋ถํฐ id ํ๋ผ๋ฏธํฐ๋ฅผ ๊ฐ์ ธ์ ํด๋น id์ ํด๋นํ๋ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๊ณ , ํผ ์ปดํฌ๋ํธ์์๋ ์กฐํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก ๊ธฐ๋ณธ๊ฐ์ด ์ธํ ๋๋ฉฐ, ์ด๋ฅผ ์์ ํ๊ณ ์ ๋ฐ์ดํธํ ๊ฒฝ์ฐ ๋ฃจํธ("/")๋ก ๋ผ์ฐํ ๋๋ ํ์ด์ง์ ๋๋ค.
์ ๋ฐ์ดํธ ํ์ด์ง์์ ์ฐ๋ฆฌ๊ฐ ๊ธฐ๋ํ๋ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- url ํ๋ผ๋ฏธํฐ๋ก ์ฃผ์ด์ง id๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ํจ์๊ฐ ํธ์ถ๋๋ค.
- ๋ก๋ฉ ์ค์๋ ๋ก๋ฉ ํ๋ฉด์ด ๋ ธ์ถ๋๋ค.
- ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ์๋ฌ ๋ฌธ๊ตฌ๊ฐ ๋ ธ์ถ๋๋ค.
- ๋ฐ์ดํฐ๊ฐ ์ ์์ ์ผ๋ก ์กฐํ๋์์ ๊ฒฝ์ฐ
"Update Data"๋ฌธ๊ตฌ๊ฐ ๋ ธ์ถ๋๊ณ , DataForm ์ปดํฌ๋ํธ์ defaultValue๊ฐ์ผ๋ก ๋ฐ์ดํฐ๊ฐ ์ ๋ฌ๋๋ค. - ํผ์์ ์ ์ถ์ด ์ ์์ ์ผ๋ก ์ด๋ฃจ์ด์ง๋ฉด ๋ฃจํธ("/")๋ก ํ์ด์ง ์ด๋์ด ๋ฐ์ํฉ๋๋ค.
์์ ๊ธฐ๋์ฌํญ๋ค์ ๊ธฐ๋ฐ์ผ๋ก ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด ์๋์ ๊ฐ์ต๋๋ค.
jest.mock('./useFetchData', () => ({
useFetchData: jest.fn(),
}))
jest.mock('./useUpdateData', () => ({
useUpdateData: jest.fn(),
}))
jest.mock('./DataForm', () => ({
DataForm: jest.fn(),
}))
describe('UpdatePage', () => {
beforeEach(() => {
useFetchData.mockImplementation(() => ({}))
useUpdateData.mockImplementation(() => ({}))
DataForm.mockImplementation(() => null)
})
it('fetches the data for the given id', () => {
renderWithRouter(() => <Route path=":id" element={<UpdatePage />} />, '/test-id')
expect(useFetchData).toHaveBeenCalledWith('test-id')
})
describe('while loading', () => {
it('renders a loader', () => {
useFetchData.mockImplementation(() => ({
isLoading: true,
}))
const { getByTestId } = renderWithRouter(
() => <Route path=":id" element={<UpdatePage />} />,
'/test-id',
)
expect(getByTestId('loader')).toBeTruthy()
})
})
describe('with an error', () => {
it('renders an error message', () => {
useFetchData.mockImplementation(() => ({
isError: true,
error: { message: 'Something went wrong' },
}))
const { container } = renderWithRouter(
() => <Route path=":id" element={<UpdatePage />} />,
'/test-id',
)
expect(container.innerHTML).toMatch('Error: Something went wrong')
})
})
describe('with data', () => {
it('renders the update page title and the data form', () => {
useFetchData.mockImplementation(() => ({
data: { foo: 'bar' },
}))
const { container } = renderWithRouter(
() => <Route path=":id" element={<UpdatePage />} />,
'/test-id',
)
expect(container.innerHTML).toMatch('Update Data')
expect(DataForm).toBeCalledWith(
expect.objectContaining({
defaultValues: { foo: 'bar' },
}),
{},
)
})
describe('on data form submit', () => {
it('updates the data and navigates to the root page', async () => {
DataForm.mockImplementation(({ onFormSubmit }) => (
<button onClick={() => onFormSubmit({ foo: 'bar' })}>Submit</button>
))
const mutateAsync = jest.fn()
useUpdateData.mockImplementation(() => ({ mutateAsync }))
const { getByText, history } = renderWithRouter(
() => (
<>
<Route path=":id" element={<UpdatePage />} />
<Route path="/" element={<div />} />
</>
),
'/test-id',
)
fireEvent.click(getByText('Submit'))
expect(mutateAsync).toHaveBeenCalledWith({
foo: 'bar',
id: 'test-id',
})
await waitFor(() => {
expect(history.location.pathname).toEqual('/')
})
})
})
})
})9. ์ฐธ๊ณ
Mocking
ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ๋ ์์กด์ ์ธ ๋ถ๋ถ์ ์ง์ ์์ฑํ๊ธฐ๊ฐ ๋ถ๋ด์ค๋ฝ๊ธฐ ๋๋ฌธ์ ๋ชจํน์ ๋ง์ด ์ฌ์ฉํฉ๋๋ค. ํ ์คํธํ๊ณ ์ถ์ ๊ธฐ๋ฅ์ด ๋ค๋ฅธ ๊ธฐ๋ฅ๋ค๊ณผ ์ฎ์ฌ ์์ ๊ฒฝ์ฐ ์ ํํ ํ ์คํธ๋ฅผ ํ๊ธฐ ํ๋ค๊ธฐ ๋๋ฌธ์, ํด๋น ์ฝ๋๊ฐ ์์กดํ๋ ๋ถ๋ถ์ ๊ฐ์ง๋ก ๋์ฒดํ๋ ๊ฒ์ ๋ชจํน์ด๋ผ๊ณ ํฉ๋๋ค.
jest.fn()
Jest๋ ๊ฐ์ง ํจ์(mock function)๋ฅผ ์์ฑํ ์ ์๋๋ก jest.fn() ํจ์๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ด๋ฅผ ์ด์ฉํด์ ์ผํ์ฑ ํ ์คํธ์ฉ์ผ๋ก ๋ด๋ถ์ ํจ์๋ฅผ ์ง์ง์ฒ๋ผ ๊ตฌ๋์์ผ ์ฝ๋๋ฅผ ์คํํ ์ ์์ต๋๋ค.
jest.mock()
jest.fn()์ ๊ฐ๋ณ์ ์ผ๋ก ํ๋ํ๋ ๋ชจํน ์ฒ๋ฆฌ๋ฅผ ํด์ค ๋ ์ฌ์ฉํ๋ฉฐ, jest.mock()์ ๊ทธ๋ฃน์ผ๋ก ํ ๋ฒ์ ๋ชจํน ์ฒ๋ฆฌ๋ฅผ ํด์ค ๋ ์ฌ์ฉํฉ๋๋ค.
Jest API
๋ค์์ Jest๋ก ๋จ์ ํ ์คํธ ์ฝ๋๋ฅผ ๊ตฌํํ ๋ ์์ฃผ ์ฌ์ฉ๋๋ API ๋ชฉ๋ก์ ๋๋ค.
describe
์ฌ๋ฌ ๊ฐ์ ํ ์คํธ ์ผ์ด์ค๋ฅผ ๋ฌถ์ ๋ ์ฌ์ฉํ๋ฉฐ, ์์ ๋จ์์ ํ ์คํธ ์ฝ๋๋ฅผ ๊ทธ๋ฃนํํ ์ ์์ต๋๋ค.
it๊ณผ test
it๊ณผ test ๋ ๊ฐ์ง ๋ชจ๋ ํ๋์ ํ ์คํธ ์ผ์ด์ค๋ฅผ ์๋ฏธํ๋ฉฐ ์์ ํ ๋์ผํ ๊ธฐ๋ฅ์ ํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก it์ test์ ๋ณ์นญ์ด๋ฏ๋ก ๊ธฐ๋ฅ์ ์ผ๋ก ๋์ผํฉ๋๋ค.
expect
expect()์๋ ์ฃผ๋ก ํ
์คํธ ์
๋ ฅ ๊ฐ ๋๋ ๊ธฐ๋๊ฐ์ ๋ฃ์ต๋๋ค.
toBe
ํ ์คํธ์ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ๋ API์ ๋๋ค. ํ ์คํธ์ ์์ ๊ฒฐ๊ณผ ๊ฐ์ ๋ฃ์ต๋๋ค.
beforeEach
ํ
์คํธ ํ์ผ์ ๊ฐ ํ
์คํธ ์ฝ๋๊ฐ ๋๊ธฐ ์ ์ ์ํํ ๋ก์ง์ ๋ฃ๋ API์
๋๋ค. ํ
์คํธ ์ผ์ด์ค๋ง๋ค ๋ฐ๋ณต๋๋ ๋ก์ง์ ๋ฃ๊ธฐ์ ์ ํฉํฉ๋๋ค.
RTL์ ๋ค์ํ Query
RTL์๋ ์๋์ ๊ฐ์ด Variant์ Query๊ฐ ์กด์ฌํ๋ฉฐ, ์ด ๋์ ์กฐํฉ์ ํตํด ํน์ DOM์ ์ ํํ ์ ์์ต๋๋ค.
Variant
getBy
getBy*๋ก ์์ํ๋ ์ฟผ๋ฆฌ๋ ์กฐ๊ฑด์ ์ผ์นํ๋ DOM ์๋ฆฌ๋จผํธ ํ๋๋ฅผ ์ ํํฉ๋๋ค. ๋ง์ฝ์ ์์ผ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
getAllBy
getAllBy*๋ก ์์ํ๋ ์ฟผ๋ฆฌ๋ ์กฐ๊ฑด์ ์ผ์นํ๋ DOM ์๋ฆฌ๋จผํธ ์ฌ๋ฌ ๊ฐ๋ฅผ ์ ํํฉ๋๋ค. ๋ง์ฝ์ ํ๋๋ ์์ผ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
queryBy
queryBy*๋ก ์์ํ๋ ์ฟผ๋ฆฌ๋ ์กฐ๊ฑด์ ์ผ์นํ๋ DOM ์๋ฆฌ๋จผํธ ํ๋๋ฅผ ์ ํํฉ๋๋ค. ๋ง์ฝ์ ์กด์ฌํ์ง ์์๋ ์๋ฌ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.
queryAllBy
queryAllBy*๋ก ์์ํ๋ ์ฟผ๋ฆฌ๋ ์กฐ๊ฑด์ ์ผ์นํ๋ DOM ์๋ฆฌ๋จผํธ ์ฌ๋ฌ ๊ฐ๋ฅผ ์ ํํฉ๋๋ค. ๋ง์ฝ์ ์กด์ฌํ์ง ์์๋ ์๋ฌ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.
findBy
findBy*๋ก ์์ํ๋ ์ฟผ๋ฆฌ๋ ์กฐ๊ฑด์ ์ผ์นํ๋ DOM ์๋ฆฌ๋จผํธ ํ๋๊ฐ ๋ํ๋ ๋๊น์ง ๊ธฐ๋ค๋ ธ๋ค๊ฐ ํด๋น DOM์ ์ ํํ๋ Promise๋ฅผ ๋ฐํํฉ๋๋ค. ๊ธฐ๋ณธ timeout์ธ 4500ms ์ดํ์๋ ๋ํ๋์ง ์์ผ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
findAllBy
findAllBy*๋ก ์์ํ๋ ์ฟผ๋ฆฌ๋ ์กฐ๊ฑด์ ์ผ์นํ๋ DOM ์๋ฆฌ๋จผํธ ์ฌ๋ฌ ๊ฐ๊ฐ ๋ํ๋ ๋๊น์ง ๊ธฐ๋ค๋ ธ๋ค๊ฐ ํด๋น DOM์ ์ ํํ๋ Promise๋ฅผ ๋ฐํํฉ๋๋ค. ๊ธฐ๋ณธ timeout์ธ 4500ms ์ดํ์๋ ๋ํ๋์ง ์์ผ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
Queries
ByLabelText
ByLabelText๋ label์ด ์๋ input์ label ๋ด์ฉ์ผ๋ก input์ ์ ํํฉ๋๋ค.
ByPlaceholderText
ByPlaceholderText๋ placeholder ๊ฐ์ผ๋ก input ๋ฐ textarea๋ฅผ ์ ํํฉ๋๋ค.
ByText
ByText๋ ์๋ฆฌ๋จผํธ๊ฐ ๊ฐ์ง๊ณ ์๋ ํ
์คํธ ๊ฐ์ผ๋ก DOM์ ์ ํํฉ๋๋ค.
์ฐธ๊ณ ๋ก ํ ์คํธ ๊ฐ์ ์ ๊ท์์ ๋ฃ์ด๋ ์๋ํฉ๋๋ค.
ByAltText
ByAltText๋ alt ์์ฑ์ ๊ฐ์ง๊ณ ์๋ ์๋ฆฌ๋จผํธ(์ฃผ๋ก img)๋ฅผ ์ ํํฉ๋๋ค.
ByTitle
ByTitle์ title ์์ฑ์ ๊ฐ์ง๊ณ ์๋ DOM ํน์ title ์๋ฆฌ๋จผํธ๋ฅผ ์ง๋๊ณ ์๋ SVG๋ฅผ ์ ํํ ๋ ์ฌ์ฉํฉ๋๋ค.