일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- Primitive
- Unit Test
- DAILY
- docker-compose
- test
- svgr
- 알고리즘
- 프론트엔드
- Study
- type
- typescript
- jest
- javascript
- Solid
- 다이나믹프로그래밍
- Component
- nextjs
- 리액트
- Docker
- 백준
- DFS
- next.js
- 타입스크립트
- 자바스크립트
- react
- SVG
- BFS
- error
- 타입
- 아키텍처
- Today
- Total
`프론트엔드 개발자` 개형이의 벽돌집
리액트 앱 단위테스트 해보기 (2) - react testing library, jest 본문
지난 포스팅에 이어 테스트코드 작성 방법에 대해 기록해보려고 한다.
테스트 코드 Arrange, Act, Assert
🤔 테스트 코드의 작성은 Arrange, Act, Assert 세가지의 세팅을 해준다고 보면 된다.
test('loads and displays greeting', async () => {
// Arrange
// Act
// Assert
})
먼저 테스트할 컴포넌트 예시 코드를 하나 작성해봤다.
// Hello.jsx
const Hello = () => {
return (
<div>
Hello World
</div>
)
}
export default Hello
위 컴포넌트에서 Hello World가 제대로 렌더링되는지 테스트하고 싶다.
우선 테스트 코드를 만들어주는데 {name}.test.js 와 같은 네이밍으로 만든다. (위 코드의 테스트코드는 Hello.test.js)
// Hello.test.js
import Hello from './Hello'
import { render, screen } from '@testing-library/react'
test('renders Hello World as a text', () => {
// Arrange: test data를 셋업
render(<Hello />)
// Act: 버튼 클릭에 대한 시뮬레이션 등등 함수, 로직을 실행
// Assert: 브라우저의 아웃풋을 확인하고 기대값과 비교 (가상 DOM을 확인)
const helloWorldElement = screen.getByText('Hello World', { exact: false })
// or screen.getByText('Hello World')
expect(helloWorldElement).toBeInTheDocument()
})
위 코드의 주석을 보면 알 수 있듯이 테스트 코드는 Arange, Act, Assert의 단계로 작성된다.
Hello World가 제대로 렌더되는지 테스트하는 부분에서 유저의 행동을 테스트하진 않으므로 Act 단계는 생략되었다.
코드에 대한 자세한 설명은 https://brick-house.tistory.com/25 을 참고하자.
테스트 그룹화 describe
🤔 애플리케이션의 사이즈가 커질수록 테스트 파일은 많아지므로 그룹화하여 관리할 필요가 있다.
describe를 활용해보자 !
import Hello from './Hello'
import { render, screen } from '@testing-library/react'
// Hello Component라는 그룹(Test Suite)에 하위 테스트가 포함된다.
describe('Hello Component', () => {
test('renders Hello World as a text', () => {
// ~~~~~
})
})
사용자 상호 작용, state 테스트
🤔 '@testing-library/user-event'를 import하여 userEvent 트리거를 테스트할 수 있다.
//...
import userEvent from '@testing-library/user-event'
// 버튼을 클릭했을 경우 changed가 render, 클릭 안했을 경우는 'hello'가 render.
test('renders "hello" if the button was NOT clicked', () => {
render(<Hello />)
const outputElement = screen.getByText('hello', { exact: false })
expect(outputElement).toBeInTheDocument()
})
test('renders "changed" if the button was clicked', () => {
// Arrange
render(<Hello />)
// Act
const buttonElement = screen.getByRole('button')
userEvent.click(buttonElement)
// Assert
const outputElement = screen.getByText('changed')
expect(outputElement).toBeInTheDocument()
})
test('does not render "hello" if the button was clicked', () => {
// Arrange
render(<Hello />)
// Act
const buttonElement = screen.getByRole('button')
userEvent.click(buttonElement)
// Assert
const outputElement = screen.queryByText('hello', { exact: false })
// queryByText를 이용하면 못찾을 경우 null을 반환,
// getByText를 사용하면 못찾을 경우 테스트 fail 된다.
expect(outputElement).toBeNull()
})
- {exact: false} 를 옵션으로 적용하면 정확히 텍스트가 일치하지 않아도 텍스트가 포함된 Element를 찾아 반환한다.
- Hello 컴포넌트에 버튼이 하나밖에 없기 때문에 getByRole('button')을 통해 클릭한 버튼을 가져올 수 있다.
- 클릭했을 경우 changed가 보여야할 뿐만 아니라 hello 텍스트가 보여지면 안되는 부분도 있기 때문에 두 경우 모두 테스트 코드를 넣어준다.
비동기 테스트와 Mock
🤔 비동기 데이터를 가져올 때에는 get대신 find를 사용해야 한다. 우선 테스트할 컴포넌트 예시는 아래와 같다.
// api 호출 결과를 리스트로 보여주는 컴포넌트. 쉽게 이해할 수 있다.
const Async = () => {
const [data, setData] = useState([]);
useEffect(() => {
fetch('api url')
.then((response) => response.json())
.then((data) => {
setData(data);
});
}, []);
return (
<div>
<ul>
{data.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};
위 컴포넌트에 리스트가 잘 보여지는지를 테스트하기 위해서는 비동기 테스트가 가능해야 한다.
(데이터를 가져오는데 시간이 걸리기 때문..)
비동기를 지원하는 테스트 코드를 작성해보자.
import { render, screen } from '@testing-library/react'
import Async from './Async'
describe('async and mock', () => {
test('renders datas if request succeeds', async () => {
render(<Async />)
// getAllByRole 을 사용할 경우 즉시 조회하기 때문에 비동기로 데이터를 가져오는 것에 대한 확인은 불가능하다.
// const listItemElements = screen.getAllByRole('listitem')
// findAllByRole 을 사용할 경우 프로미스를 반환한다.
// HTTP 요청이 성공할 때까지 기다리게 되며 세번째 인자에 timeout 기간을 정하여 테스트가 가능 ('listitem', {exact: ~~}, {timeout: ~~ })
// default timeout은 1초이다.
const listItemElements = await screen.findAllByRole('listitem') // 프로미스를 반환하므로 앞에 await!
expect(listItemElements).not.toHaveLength(0)
})
})
위 코드 주석에서 확인할 수 있듯이 getAllByRole을 사용할 경우는 비동기로 데이터를 가져오는 것에 대한 테스트가 불가능하므로,
findAllByRole (Promise 타입으로 반환)을 사용해서 Element를 가져와야 한다.
하지만 위와 같이 테스트를 할 경우 fetch를 통해 서버의 데이터가 변경될 경우도 있기 때문에
위 방식이 최선의 테스트는 아니라고 하는데…
이때 사용해야 하는 것이 Mock (모의) 이다.
😀 모의 작업 (Mock)
테스트를 실행할 때 일반적으로 서버에 HTTP 요청을 전송하지 않아야 한다.
왜?
- 네트워크 트래픽을 과도하게 일으켜 서버가 요청들로 인해 과부화가 될 수 있다. (많은 api 테스트가 존재할 경우)
- 테스트로 서버에 POST 요청을 일으키거나 하면 서버 데이터가 변경될 가능성이 있다.
⇒ mock (더미) function을 작성하여 테스트에 이용하는 방식으로 해결 가능한데 아래 코드를 살펴보자.
import { render, screen } from '@testing-library/react'
import Async from './Async'
describe('async and mock', () => {
test('renders datas if request succeeds', async () => {
// jest.fn 은 mock function을 만들어준다.
window.fetch = jest.fn()
window.fetch.mockResolvedValueOnce({
// json 형태로 response 리턴되도록.
json: async () => [{id: '01', name: 'num1'}]
})
render(<Async />)
const listItemElements = await screen.findAllByRole('listitem')
expect(listItemElements).not.toHaveLength(0)
})
})
mock을 이용해 더미 데이터를 생성하여 api 요청을 일으키지 않고 리스트 아이템이 렌더링되는지 확인할 수 있다!
JEST: https://jestjs.io/docs/getting-started
React-Testing-Library: https://testing-library.com/docs/react-testing-library/intro/
'테스트' 카테고리의 다른 글
내가 볼려고 만든 Cypress 간단 정리. Get, before, it, describe (0) | 2023.09.25 |
---|---|
리액트 앱 단위테스트 해보기 (1) - with create react app(CRA) (0) | 2022.06.18 |