- ๋ค๋ฅธ ์ฌ๋์ด ์์ฑํ ์์ค ์ฝ๋ ๋๋ ๋ด๊ฐ ์์ ์ ์์ฑํ ์ฝ๋์ ๋ํด ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๋๋ฐ ์ด ์ฝ๋๊ฐ ์ด๋ป๊ฒ ๋์ํ๋์ง ์ ์ ์๊ฑฐ๋ ๊ธฐ์ตํ ์ ์์์ผ๋ก ์ฝ๋๋ฅผ ๋ค์ ๋ถ์ํ๊ณ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๋ค.
- ์์ฑ๋ ์ฝ๋๊ฐ ํ ์คํธํ๊ธฐ ์ฝ๊ฒ ์์ฑ๋์ด ์์ง ์๋ค. ๋ฐ๋ผ์ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ๊ฐ ๋ถ๊ฐ๋ฅ ํ๊ฑฐ๋ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ํด ์ด๋ฏธ ์์ฑ๋ ์ฝ๋๋ฅผ ์์ ํด์ผ ํ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํ๋ค. ์ด๋ ๊ธฐ์กด ์ฝ๋์ ์์ ์ผ๋ก ์ธํด ์๊ธฐ์น ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
Jest ํ ์คํธ ํ๋ ์์ํฌ๋ก ํ ์คํธ ์ฝ๋ ์์ฑํ๊ธฐ
Jest์ ์ฃผ์ ์ฟผ๋ฆฌ ํจ์
describe ํจ์
- ์ฒซ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ ๋ช ๋ น ํ๋กฌํํธ์ ํ์ํ ์ค๋ช
- ๋๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ ์ฌ๋ฌ ํ ์คํธ๋ฅผ ๊ทธ๋ฃน์ผ๋ก ๋ฌถ์ ์ฝ๋ฐฑ ํจ์
describe('test index.js file', () => { });
it ํจ์
- ์ฒซ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ ํ ์คํธ ๋ช ์ธ์ ์ค๋ช
- ๋ ๋ฒ์งธ ๋งค๊ฐ๋ณ์์๋ ์ค์ ๋ก ํ ์คํธ๋ฅผ ์คํํ๋ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑ
it('sum 1+2 to equal 3', () => { });
expect ํจ์
it('sum 1+2 to equal 3', () => { expect(sum(1,2)).toBe(3); });
- ์ฌ๊ธฐ์ sum(1,2)๋ ํ ์คํธ ๋์์ด ์ค์ ๋ก ์์ฑํ ๊ฐ์ด๊ณ , 3๋ ๊ธฐ๋ํ๋ ๊ฐ์ด๋ฉฐ toBe๋ ์ผ์น ์ฌ๋ถ๋ฅผ ํ์ธํ๋ ๋ฉ์๋์ด๋ค.
// ๊ฐ์ฒด๋ ๋ฐฐ์ด์ ๋๋ฑ์ฑ ๋น๊ต expect(actualValue).toEqual(expectedValue); // ๋ฐฐ์ด์ด ํน์ ์์ดํ ์ ํฌํจํ๋์ง expect(array).toContain(item); // ํจ์๊ฐ ํน์ ์ค๋ฅ๋ฅผ ๋์ง๋์ง expect(() => functionCall()).toThrow(ErrorType);
@testing-library๋ก ๋ฆฌ์กํธ ์ปดํฌ๋ํธ ํ ์คํธ ์ฝ๋ ์์ฑ ํ๊ธฐ
ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ๋ ์ปดํฌ๋ํธ ์ธ๋ถ ๊ตฌํ์ฌํญ์ ํฌํจํ์ง ์์ผ๋ฉด์๋ ์ ๋ขฐํ ์ ์๋ ํ ์คํธ ์ฝ๋ ์์ฑ์ ๋์์ ์ค๋ค. ์ด๋ ๊ฒ ์ปดํฌ๋ํธ์ ์ธ๋ถ ๊ตฌํ ์ฌํญ์ ํฌํจํ์ง ์์ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด ์ปดํฌ๋ํธ์ ์ธ๋ถ ๊ตฌํ ๋ถ๋ถ์ ๋ฆฌํฉํ ๋ง ํ์ฌ๋ ํ ์คํธ ์ฝ๋๋ฅผ ์์ ํ ํ์๊ฐ ์๋ค, ์ด๋ก ์ธํด ํ๋ฒ ์์ฑํ ํ ์คํธ ์ฝ๋๋ ๊ธด ์๊ฐ ์ ์งํ ์ ์์ผ๋ฉฐ ์ค๋ ๊ธฐ๊ฐ ์ ์ง ๊ฐ๋ฅํ์ฌ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฃผ ์์ ํ์ง ์์๋ ๋๋ฏ๋ก ๊ฐ๋ฐ ์์ฐ์ฑ์ ํฅ์ ์์ผ ์ค๋ค.
App.js ์ปดํฌ๋ํธ - ๋ ๋๋ง ํ ์คํธ
import { render, screen } from 'testing-library/react'; import App from './App.js'; test('renders learn react link', ()=>{ render(<App />); const linkElement = screen.getByText(/learn react/i); expect(linkElement).toBeInTheDocument(); })
test
render
screen
GetByText
import { render, screen } from '@testing-library/react'; import App from './App'; test('renders Hello World text', () => { render(<App />); const textElement = screen.getByText('Hello World'); expect(textElement).toBeInTheDocument(); });
toBeInTheDocument
render ํจ์๋ ๋ฉ๋ชจ๋ฆฌ์์ ๋์ ๋ง๋ค๊ณ screen์ ํตํด ํด๋น ๋์ ์ ๊ทผ ํ๋ ๊ฒ์ ์๋ฏธํ๋ค. react-testing-library์ render ํจ์๋ฅผ ์ฌ์ฉํ์ฌ App์ด๋ผ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋ง ํ์๋ค.
React Testing Library์ ์ฃผ์ ์ฟผ๋ฆฌ ํจ์
getByText
ex) screen.getByText(/learn react/i)๋ ๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํ์ง ์๊ณ "learn react"๋ผ๋ ํ ์คํธ๋ฅผ ํฌํจํ๋ ์์๋ฅผ ์ฐพ์ต๋๋ค.
import { render, screen } from '@testing-library/react'; import App from './App'; test('renders learn react link', () => { render(<App />); const linkElement = screen.getByText(/learn react/i); expect(linkElement).toBeInTheDocument(); });
getByRole
screen.getByRole('button', { name: /submit/i })์ "submit" ํ ์คํธ๋ฅผ ๊ฐ์ง ๋ฒํผ์ ์ฐพ์ต๋๋ค.
import { render, screen } from '@testing-library/react'; import App from './App'; test('renders submit button', () => { render(<App />); const buttonElement = screen.getByRole('button', { name: /submit/i }); expect(buttonElement).toBeInTheDocument(); });
getByLabelText
screen.getByLabelText('Username')์ "Username" ๋ผ๋ฒจ์ ๊ฐ์ง ์ ๋ ฅ ํ๋๋ฅผ ์ฐพ์ต๋๋ค.
import { render, screen } from '@testing-library/react'; import LoginForm from './LoginForm'; test('finds the username input', () => { render(<LoginForm />); const inputElement = screen.getByLabelText('Username'); expect(inputElement).toBeInTheDocument(); });
getByPlaceholderText
screen.getByPlaceholderText('Enter your username')์ "Enter your username" ํ๋ ์ด์คํ๋๋ฅผ ๊ฐ์ง ์ ๋ ฅ ํ๋๋ฅผ ์ฐพ์ต๋๋ค.
import { render, screen } from '@testing-library/react'; import LoginForm from './LoginForm'; test('finds the input by placeholder', () => { render(<LoginForm />); const inputElement = screen.getByPlaceholderText('Enter your username'); expect(inputElement).toBeInTheDocument(); });
getByAltText
screen.getByAltText('Profile Picture')์ "Profile Picture" alt ํ ์คํธ๋ฅผ ๊ฐ์ง ์ด๋ฏธ์ง๋ฅผ ์ฐพ์ต๋๋ค.
import { render, screen } from '@testing-library/react'; import Profile from './Profile'; test('finds the profile picture', () => { render(<Profile />); const imageElement = screen.getByAltText('Profile Picture'); expect(imageElement).toBeInTheDocument(); });
getByTestId
screen.getByTestId('custom-element')์ data-testid="custom-element" ์์ฑ์ ๊ฐ์ง ์์๋ฅผ ์ฐพ์ต๋๋ค.
import { render, screen } from '@testing-library/react'; import Component from './Component'; test('finds element by test id', () => { render(<Component />); const element = screen.getByTestId('custom-element'); expect(element).toBeInTheDocument(); });
queryByText
getByText๋ ์์๊ฐ ์์ผ๋ฉด ์๋ฌ๋ฅผ ๋์ง๋๋ค.
import { render, screen } from '@testing-library/react'; import App from './App'; test('does not find non-existing text', () => { render(<App />); const textElement = screen.queryByText('Non-existing Text'); expect(textElement).toBeNull(); });
findByText
import { render, screen } from '@testing-library/react'; import App from './App'; test('renders welcome message', async () => { render(<App />); const messageElement = await screen.findByText(/welcome/i); expect(messageElement).toBeInTheDocument(); });
์ฐธ๊ณ ํ ๋งํ ์์ ๋ค
์์ [1] - ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๋ ๋๋ง๋๋์ง๋ฅผ ํ์ธํ๋ ํ ์คํธ ์ผ์ด์ค
๊ด๋ จ ํค์๋
render
getElementByClassName
toHaveLength
toHaveAttribute
describe('<App />', ()=>{ it('renders component correctly', ()=>{ โ const { container } = render(<App />); โก expect(container.getElementByClassName('App-logo')).toHaveLength(1); โข expect(container.getElementByClassName('App-logo')[0]).toHaveAttribute('src','logo.svg'); }) })
์์ [2] - <img /> ํ ์คํธ ์ฝ๋
๊ด๋ จ ํค์๋
render
getElementsByTagName
toHaveLength
toHaveTextContext
describe('<App />', ()=>{ it('renders component correctly', ()=>{ โ const { container } = render(<App />); โก expect(container.getElementsByTagName('p')).toHaveLength(1); โข expect(container.getElementsByTagName('p')[0]).toHaveTextContext('Edit src/App.js and save to reload.') }) })
ํ๊ทธ๋ฅผ ๊ฐ์ง ์์๊ฐ ํ๋ ์๋์ง ํ์ธํ๋ค. expect ํจ์๋ ๊ธฐ๋๊ฐ์ ์ค์ ํ๋ ๋ฐ ์ฌ์ฉ๋๊ณ , toHaveLength(1)๋ ํด๋น ์์์ ๊ธธ์ด๊ฐ 1์ธ์ง ํ์ธํ๋ค. โข ์ด ์ค์
ํ๊ทธ๋ฅผ ๊ฐ์ง ์ฒซ ๋ฒ์งธ ์์๊ฐ 'Edit src/App.js and save to reload.'๋ผ๋ ํ ์คํธ ๋ด์ฉ์ ๊ฐ์ง๊ณ ์๋์ง ํ์ธํ๋ค. expect ํจ์๋ ๊ธฐ๋๊ฐ์ ์ค์ ํ๋ ๋ฐ ์ฌ์ฉ๋๊ณ , toHaveTextContent('Edit src/App.js and save to reload.')๋ ํด๋น ์์์ ํ ์คํธ ๋ด์ฉ์ด ์ ํํ ์ผ์นํ๋์ง๋ฅผ ํ์ธํ๋ค.
์์ [3] - <p/> ํ ์คํธ
describe('<App />', ()=>{ it('renders component correctly', ()=>{ const { container } = render(<App />); expect(container).toMatchSnapshot(); }) })
์ ์ฅ๋ ์ค๋ ์ท์ App ์ปดํฌ๋ํธ๊ฐ ์์ ๋์ด ํ๋ฉด์ ํ์๋๋ HTML ๊ตฌ์กฐ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ์๋ฌ๋ฅผ ํ์ํ๊ฒ ๋๋ค. ์ด๋ ๊ฒ ์ค๋ ์ท์ ํ๋ฉด์ ํ์๋๋ ์ปดํฌ๋ํธ๊ฐ ๋ณ๊ฒฝ๋์๋์ง ๊ฐ์งํ๊ธฐ ์ํ ํ ์คํธ๋ก ๋ง์ด ์ฌ์ฉ๋๋ค.
์์ [4] - ์ค๋ ์ท (ํ๋ฉด์ ํ์๋๋ ๋ด์ฉ์ด ๋ณ๊ฒฝ ๋์๋์ง ์ฒดํฌ)
๊ด๋ จ ํค์๋
getByText
parentElement
toHaveStyleRule
import React from 'react'; import { render, screen } from 'testing-library/react'; import 'jest-styled-components'; import {Button} from './index'; describe('<Button />', ()=>{ it('renders component correctly', ()=>{ โ const { container } = render(<Button label="Button Test" />); โก const label = screen.getByText("button Test"); expect(label).toBeInTheDocument(); โข const parent = label.parentElement; โฃ expect(parent).toHaveStyleRule('background-color', '#304FFE'); expect(parent).toHaveStyleRule('background-color', '#1E40FF', { modifier:'hover'}); โค expect(container).toMatchSnapshot(); }) })
โก screen.getByText๋ฅผ ์ฌ์ฉํ์ฌ "Button Test"๋ผ๋ ํ ์คํธ๋ฅผ ๊ฐ์ง ์์๋ฅผ ์ฐพ๋๋ค. expect๋ก ํด๋น ์์๊ฐ ๋ฌธ์์ ์กด์ฌํ๋์ง ํ์ธํ๋ค.
โข label ์์์ ๋ถ๋ชจ ์์(parentElement)๋ฅผ ๊ฐ์ ธ์จ๋ค.
โฃ expect๋ฅผ ์ฌ์ฉํ์ฌ ๋ถ๋ชจ ์์๊ฐ ํน์ ์คํ์ผ ๊ท์น์ ๊ฐ์ง๊ณ ์๋์ง ํ์ธํ๋ค. jest-styled-components์ toHaveStyleRule์ ์ฌ์ฉํ์ฌ ๋ค์ ๋ ๊ฐ์ง ์คํ์ผ ๊ท์น์ ํ์ธํ๋ค.
- ๊ธฐ๋ณธ ๋ฐฐ๊ฒฝ์์ด #304FFE์ธ์ง
- hover ์ํ์์ ๋ฐฐ๊ฒฝ์์ด #1E40FF์ธ์ง
Button ์ปดํฌ๋ํธ๋ Props๋ก BackgroundClolor๊ณผ hoverColor์ด ์ค์ ๋์ด ์์ง ์์ผ๋ฉด ๊ธฐ๋ณธ๊ฐ์ด ์ค์ ๋๋๋ก ๊ฐ๋ฐ ๋์๋ค. backgroundColor๊ณผ hoverColor๊ฐ ์ค์ ๋์ด ์์ง ์์ ์ํฉ์์ ๊ธฐ๋ณธ๊ฐ์ด ์ ์ค์ ๋๋์ง ํ์ธํ๊ธฐ ์ํด jest-styled-components์ ์๋ก์ด Matcher์ธ toHaveStyleRule๋ฅผ ์ฌ์ฉํ์ฌ ํ์ธ ํ๋๋ก ํ๋ค.
์์ [4] - onClick ํจ์ ํ ์คํธ
it('clicks the button', () => { โ const handlerClick = jest.fn(); โก render(<Button label="Button Test" onClick={handlerClick} />); โข const label = screen.getByText('Button Test'); โฃ expect(handleClick).toHaveBeenCalledTimes(0); โค fireEvent.click(label); โฅ expect(handlerClick).toHaveBeenCalledTimes(1); });
โก Button ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๊ณ , onClick props์ mock ํจ์ ์ ๋ฌ
โข ํ๋ฉด์์ "Button Test" ๋ผ๋ ํ ์คํธ๋ฅผ ๊ฐ์ง ์์๋ฅผ ์ฐพ์
โฃ ํด๋ฆญ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ง ์์๋์ง ํ์ธ
โค ์ฐพ์ ์์์ ํด๋ฆญ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํด
โฅ ํด๋ฆญ ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ํ ๋ฒ ํธ์ถ๋์๋์ง ํ์ธ
์์ [5] - <Input /> ์ ๋ ฅ๋ ๊ฐ์ด ์ผ์น ์ฒดํฌ
๊ด๋ จ ํค์๋
getByDisplayValue
getByPlaceholderText
fireEvent
fireEvent.change
๐ getByDisplayValue๋ฅผ ์ด์ฉํ์ฌ input์ ์ฐพ์ ๋ ๋๋ง ํ๋ ์์
import React from 'react'; import { render, screen, fireEvent } from 'testing-library/react'; import 'jest-styled-components'; import {Input} from './index'; describe('<Input />', ()=>{ it('renders component correctly', ()=>{ โ const { container } = render(<Input value="default value" />); โก const label = screen.getByDisplayValue("default value"); โข expect(label).toBeInTheDocument(); โฃ expect(container).toMatchSnapshot(); }) })
โก screen.getByDisplayValue ํจ์๋ ์ฃผ์ด์ง ๊ฐ("default value")์ ํ์ํ๋ ์์๋ฅผ ๊ฒ์ํฉ๋๋ค.
ใ์ฌ๊ธฐ์๋ ์ปดํฌ๋ํธ๊ฐ "default value"๋ผ๋ ๊ฐ์ ๊ฐ์ง ์ ๋ ฅ ํ๋๋ฅผ ๋ ๋๋งํ๋์ง ํ์ธํฉ๋๋ค.
โข expect ํจ์๋ ๋จ์ธ(assertion)์ ์ ์ํฉ๋๋ค. ์ฌ๊ธฐ์๋ label ์์๊ฐ ๋ฌธ์ ๋ด์ ์กด์ฌํ๋์ง ํ์ธํฉ๋๋ค. ใtoBeInTheDocument ๋งค์ฒ๋ ์์๊ฐ ์ค์ ๋ก DOM์ ์กด์ฌํ๋์ง ํ์ธํฉ๋๋ค.
๊ทธ๋์ Input ์ปดํฌ๋ํธ์ ํ์๊ฐ ์๋ Props์ธ value๊ฐ์ ์ค์ ํ๊ณ react-testing-library์ screen.getByDisplayValue๋ฅผ ์ฌ์ฉํ์ฌ input ์ปดํฌ๋ํธ๋ฅผ ์ฐพ๋๋ค.
๐ getByPlaceholderText๋ฅผ ์ด์ฉํ์ฌ input์ ์ฐพ์ ๋ ๋๋ง ํ๋ ์์
it('renders placeholder correctly', ()=>{ render(<Input placeholder="default placeholder" />); const input = screen.getByPlaceholderText("default placeholder"); expect(input).toBeInTheDocument(); })
๐ fireEvent๋ฅผ ์ด์ฉํ์ฌ ์ด๋ฒคํธ ํ ์คํธ ํ๋ ์์
import { render, screen, fireEvent } from 'testing-library/react'; it('renders placeholder correctly', ()=>{ render(<Input placeholder="default placeholder" />); const input = screen.getByPlaceholderText("default placeholder") as HTMLInputElement; fireEvent.change(input, {target:{ value:'study react'}}) expect(input.value).toBe('study react') })