아래 3가지는 cra를 하면 기본으로 설치됨
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
__test__
directory를 생성__test__/ComponentName.test.js
생성screen의 method는 아래와 같이 정리된다.
getByTestId는 element에 data-testid를 준것에 대해서 찾을 수 있다.
사용자에게 visible한지 test하기
toBeVisible을 사용
opacity로 간단하게 테스트할 수 있다.
expect(paragraphElement).toBeVisible(); // 보이는 상태라면
expect(paragraphElement).not.toBeVisible(); // 보이지 않는 상태라면
toContainHTML
특정 html이 포함되어 있는지
expect(paragraphElement).toContainHTML('p');
1개의 테스트에는 1개의 expect assertion만 있도록 한다. (여러개가 있어도 되지만, 에러가 발생하는 원인을 찾기 어렵게 만든다.)
describe로 test block을 만들 수 있음
Invariant failed: You should not use <Link> outside a <Router>
테스트할 TodoFooter.js는 아래와 같다.
import React from 'react'
import "./TodoFooter.css"
import { Link } from "react-router-dom"
function TodoFooter({
numberOfIncompleteTasks
}) {
return (
<div className="todo-footer">
<p>{numberOfIncompleteTasks} {numberOfIncompleteTasks === 1 ? "task" : "tasks"} left</p>
<Link to="/followers">Followers</Link>
</div>
)
}
export default TodoFooter
props가 제대로 넘어가는 지, 그리고 text가 제대로 나오는지 확인하기 위한 테스트를 작성한다.
// TodoFooter.test.js
import { render, screen } from '@testing-library/react';
import TodoFooter from '../TodoFooter'
it('should render the correct amount of incomplete tasks', async () => {
render(<TodoFooter numberOfIncompleteTasks={5} />)
const paragraphElement = screen.getByText(/5 tasks left/i);
expect(paragraphElement).toBeInTheDocument();
expect(paragraphElement).toBeTruthy();
})
// Invariant failed: You should not use <Link> outside a <Router> 에러가 발생한다.
TodoFooter는 기존에는 index.js에서 BrowserRouter에 의해 감싸져 있다.
// index.js
import { BrowserRouter } from "react-router-dom"
ReactDOM.render(
<BrowserRouter>
<React.StrictMode>
<App />
</React.StrictMode>
</BrowserRouter>,
document.getElementById('root')
);
하지만, unit test에서 TodoFooter는 독립적인 컴포넌트이다. 따라서 Link 에러가 발생한다.
import { render, screen } from '@testing-library/react';
import TodoFooter from '../TodoFooter'
import { BrowserRouter } from 'react-router-dom';
const MockTodoFooter = ({ numberOfIncompleteTasks }) => {
return (
<BrowserRouter>
<TodoFooter numberOfIncompleteTasks={numberOfIncompleteTasks} />
</BrowserRouter>
)
}
it('should render the correct amount of incomplete tasks', async () => {
render(<MockTodoFooter numberOfIncompleteTasks={5} />)
const paragraphElement = screen.getByText(/5 tasks left/i);
expect(paragraphElement).toBeInTheDocument();
expect(paragraphElement).toBeTruthy();
})
아래와 같이 Add 버튼을 클릭하면, List에 추가하는 컴포넌트를 테스트한다.
import React, { useState } from 'react'
import "./AddInput.css"
import { v4 } from "uuid"
import TodoList from '../TodoList/TodoList'
function AddInput({
setTodos, todos
}) {
const [todo, setTodo] = useState("")
const addTodo = () => {
let updatedTodos = [
...todos,
{
id: v4(),
task: todo,
completed: false
}
]
setTodos(updatedTodos);
setTodo("")
}
return (
<div className="input-container">
<input
className="input"
value={todo}
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task here..."
/>
<button
className="add-btn"
onClick={addTodo}
>
Add
</button>
</div>
)
}
export default AddInput
테스트는 3가지정도로 진행한다.
import { render, screen, fireEvent } from '@testing-library/react';
import AddInput from '../AddInput';
const mockedSetState = jest.fn();
describe("AddInput", () => {
// #1 input 폼이 랜더링되는 지 확인한다.
test("should render input element", async () => {
render(<AddInput
todos={[]}
setTodos={mockedSetState} // setTodo는 function인데, 테스트에서는 사용하지 않으므로 jest.fn로 빈 함수를 만들어 준다.
/>)
const inputEl = screen.getByPlaceholderText(/Add a new task here.../i)
expect(inputEl).toBeInTheDocument();
})
// #2 input 폼에 change event로 입력한 값이 input value와 같은 지 테스트한다.
test("should typable input element", async () => {
render(<AddInput
todos={[]}
setTodos={mockedSetState}
/>)
const inputEl = screen.getByPlaceholderText(/Add a new task here.../i)
fireEvent.change(inputEl, { target: { value: 'hello world' } });
expect(inputEl.value).toBe("hello world")
})
// #3 Add 버튼을 클릭시, input 폼이 clear되는 지 확인한다.
test("should clear input element when add button is clicked", async () => {
render(<AddInput
todos={[]}
setTodos={mockedSetState}
/>)
const inputEl = screen.getByPlaceholderText(/Add a new task here.../i)
const btnEl = screen.getByRole("button", { name: /Add/i })
fireEvent.click(btnEl);
expect(inputEl.value).toBe("")
})
})