1. 단위 테스트의 여러 검증법
이번 시간에는
React
,Vitest
,React Testing Library
를 사용해, 단위 테스트의 여러 검증방법을 알아보았다. 포스팅의 목차는 아래와 같다.1) 모킹으로 외부 모듈 검증하기
2) 커스텀 훅 검증하기(ex. custom Hook)
3) 비동기 검증하기(ex. timer)
- 만약 단위테스트의 정의, 작성법에 대한 사전 지식이 없다면, 이 포스팅을 먼저 보는 걸 추천한다.
1) 모킹으로 외부 모듈 검증하기
(1) 모킹이 필요한 경우는?
- 프로젝트는 여러 외부 라이브러리를 사용한다.
- 그렇다면 테스트하고 싶은 대상 컴포넌트에서 외부 라이브러리를 사용하고 있다면 어떻게 검증해야할까?
- 외부 라이브러리에 대한 검증도 해야할까? 검증이 필요할 수도 있지만, 대체로 생략 가능하다!
❓ 그럼, 어떤 경우에 외부 라이브러리에 대한 검증을 생략해도 될까?
react-router-dom
의 경우, 라이브러리 자체에서 테스트를 하고 있다. (테스트 코드)
- 이 경우 외부 모듈에 대한 검증은 생략하고, 모듈의 특정 기능이 호출되는지만 검증하면 된다.
- 이때 호출 검증은 모킹을 사용하면 된다.
(2) 모킹으로 외부 모듈의 호출을 검증하는 방법
📌 모킹(
mocking
)은 테스트 기법의 하나로, 실제 객체를 모의 객체로 대체해 테스트하는 방법이다.
모킹을 사용하면, 테스트 환경에서 외부 모듈을 테스트할 수 있다.
vitest
에서 제공하는vi.mock()
을 사용해 특정 모듈을 모킹할 수 있다.- 만약 우리가 home 글자 클릭시, “/” 경로로 이동하는지 검증하고 싶다고 가정하자.
- 그리고 경로 이동이
react-router-dom
의useNavigate()
로 구현되어 있다면? - 우선,
react-router-dom
의useNavigate()
호출을 검증하기 위해, spy함수로 모킹해야한다.
import { vi } from 'vitest';
// useNavigate 훅을 spy함수로 대체
const navigateFn = vi.fn();
vi.mock('react-router-dom', async () => {
// importActual로 모듈을 모킹
const original = await vi.importActual('react-router-dom');
return {
...original,
useNavigate: () => navigateFn,
};
});
- 그 다음, 테스트 코드에서는 모킹한
navigateFn()
가 호출되는지 검증하면 된다! - 단, 외부 모듈과 동일한 모킹 객체를 생성하는 건 비용이 발생하며, 모의 객체를 남용시 테스트 신뢰성을 낮출 수 있다는 점을 유의하자.
import { render } from '@testing-library/react';
it('"home" 링크를 클릭할 시, "/"경로로 이동하는 navigate()가 호출된다.', async () => {
// Arrange
const { user } = await render(<NoticeHeader />);
// Act
await user.click(screen.getByText('home'));
// Assert - 모킹한 함수가 호출되는지 검증만 하면 됨
expect(navigateFn).toHaveBeenNthCalledWith(1, '/');
});
(3) 모킹시 주의할 점
- 그런데, 만약 다른 테스트에서 외부 모듈에 대한 모킹이 필요없다면? 혹은 특정 테스트의 모킹 작업이 다른 테스트에 영향을 준다면?
- 이 경우, 테스트의 신뢰성을 떨어뜨릴 수 있기에 모킹 초기화가 필요하다!
- 테스트 전역 설정 파일(
setupTest.js
)에서 일괄 초기화하자.
clearAllMocks |
resetAllMocks |
---|---|
- 모킹된 모의 객체 호출에 대한 히스토리를 초기화 - 단, 모듈의 모킹은 유지됨 -모킹 모듈 기반으로 작성된 테스트 코드를 작성할 때 유용 - 반면, 모킹 히스토리가 쌓이므로 다른 테스트에 영향을 줄 수 있음 |
- 모킹 모듈에 대한 모든 구현을 초기화 - 테스트의 안전성과 신뢰성을 보장 |
// setupTest.js
afterEach(() => {
vi.clearAllMocks();
});
afterAll(() => {
vi.resetAllMocks();
});
2) 커스텀 훅 검증하기(ex. custom Hook)
(1) 커스텀 훅이란?
- 커스텀 훅이란, 특정 로직을 재사용 가능한 형태로 캡슐화하는 방법이다.
- 커스텀 훅을 사용하면, 로직의 재사용성이 높아지며 비즈니즈 로직을 분리되기에 결합도를 낮출 수 있다.
- 커스텀 훅의 검증은 React Testing Library의
renderHook()
로 가능하다.
(2) 커스텀 훅을 검증하는 방법
- 커스텀 훅 검증법을 알아보기 위해, 알림 모달의 상태를 관리하는
useAlertModal
을 가져왔다. - 이제, 2가지 테스트 케이스를 보면서, 커스텀 훅 검증 코드를 살펴보자.
// useAlertModal.tsx
import { useState } from 'react';
const useAlertModal = (initialValue = false) => {
const [isModalOpened, setIsModalOpened] = useState(initialValue);
const toggleIsModalOpened = () => {
setIsModalOpened(!isModalOpened);
};
return {
toggleIsModalOpened,
isModalOpened,
};
};
export default useAlertModal;
[테스트 케이스 1]
initialValue
가 지정되지 않는 경우isModalOpened
의 상태가false
인지
renderHook()
은 커스텀 훅을 렌더링하고, 반환된 훅을 테스트할 수 있게 해준다.
const { result, rerender } = renderHook(커스텀 훅);
반환값 | 설명 |
---|---|
result |
result 객체는 훅의 현재 반환 값에 접근할 수 있게 해줌 (ex. result.current ) |
rerender |
rerender()는 훅을 다시 렌더링할 수 있는 함수임 테스트 중 훅에 새로운 props를 제공하거나, 상태를 업데이트하여 그 변화를 확인 할 때 유용함 |
renderHook()
에useAlertModal
을 넘겨, 훅의 상태값을 검증할 수 있다.
import { renderHook } from '@testing-library/react';
import useAlertModal from './useAlertModal';
it('initialValue 인자가 지정되지 않는 경우 isModalOpened의 상태가 false가 된다.', () => {
// Arrange
const { result, rerender } = renderHook(useAlertModal);
// Assert
expect(result.current.isModalOpened).toBe(false);
});
[테스트 케이스 2]
toggleIsModalOpened()
를 호출하면,isModalOpened
상태가 변경되는지
act()
는 컴포넌트가 렌더링된 후, 상태를 변경하는 함수를 호출할 때 사용한다.- 만약
render()
혹은useEvent
모듈을 사용하지 않는 경우,act()
에서 상태 변경 함수를 호출해야한다.
act(() => {
// 상태 변경 함수 호출
})
act()
에서toggleIsModalOpened()
를 호출해, 커스텀 훅의 상태를 변경하고 이를 검증할 수 있다.
import { renderHook, act } from '@testing-library/react';
import useAlertModal from './useAlertModal';
it('toggleIsModalOpened()를 호출하면, isModalOpened 상태가 toggle된다.', () => {
// Arrange
const { result } = renderHook(() => useAlertModal(true));
// Act
act(() => {
result.current.toggleIsModalOpened(); // (c)
});
// Assert
expect(result.current.isModalOpened).toBe(false);
});
3) 비동기 검증하기(ex. timer)
- 일반적으로 테스트 코드는 동기적으로 실행된다.
- 그래서 비동기 함수가 실행되기도 전에 테스트가 종료되어 테스트가 실패할 수 있다.
- 그럼 어떻게 비동기 함수를 검증할 수 있을까? 바로 타이머를 모킹하면 된다!
(1) 비동기를 검증하는 방법, debounce 검증해보기
debounce()
는 일정 시간이 지난 후에 한 번만 실행되는 함수이다.- 주로 스크롤 이벤트나 입력창 입력 이벤트에 적용하는데, 로직의 호출 횟수를 절약해준다.
- 그럼
debounce()
의 동작을 검증하기 위해선 어떻게 해야할까? 순서대로 알아보자.
export const debounce = (fn, wait) => {
let timeout = null;
return (...args) => {
const later = () => {
timeout = -1;
fn(...args);
};
if (timeout) {
clearTimeout(timeout);
}
timeout = window.setTimeout(later, wait);
};
};
첫 번째, 타이머 모킹 준비하기
debounce()
는 타이머 함수를 사용한다. 그래서 함수를 검증하기 위해, 타이머를 모킹해야한다.useFakeTimers()
은 setTimeout, setInterval, clearTimeout, clearInterval 함수를 모킹할 수 있다.beforeEach
에서useFakeTimers()
를 호출해 타이머를 모킹해주자.
describe('debounce', () => {
beforeEach(() => {
// 타이머를 조작할 준비
vi.useFakeTimers();
});
afterEach(() => {
// 테스트 후 타이머를 원상복귀시킴
vi.useRealTimers();
});
...
});
두 번째, 타이머를 조작해, 테스트 검증하기
- 앞서,
useFakeTimers()
로 타이머 조작 준비를 했다. 이제 실제로 타이머를 조작해보자! advanceTimersByTime(time)
을 사용하면time
초가 지난 것처럼 시뮬레이션 할 수 있다.
vi.advanceTimersByTime(time);
- 만약 300ms 이후에
debounce()
가 실행되는지 테스트하고 싶다면? - 아래 코드와 같이
advanceTimersByTime
의 인자값을 300으로 설정하면 검증할 수 있다.
describe('debounce', () => {
...
it('특정 시간이 지난 후 함수가 실행된다', () => {
// Arrange
const spy = vi.fn();
const time = 300;
const debounced = debounce(spy, time);
// Act
debounced();
vi.advanceTimersByTime(time);
// Assert
expect(spy).toHaveBeenCalled();
});
...
});
2. 마치며…
- 이번 시간에는 단위테스트의 여러 검증법으로, 모듈 검증, 커스텀 훅 검증, 비동기 검증법을 알아보았다.
- 먼저, 모듈 검증의 경우 실제로 모듈 자체를 검증하기 보다는 모듈의 호출을 검증했다. 그 이유는 외부 모듈의 경우 자체적으로 검증을 거치기 때문이었는데 이 경우 모듈 내부 로직이 아닌 호출만 검증하면 된다.
- 두 번째는 커스텀 훅을 검증하는 법을 살펴봤다. 커스텀 훅은 로직을 캡슐화해 재사용성을 높이는 방법으로, 로직 결합도를 낮출 수 있는 장점이 있다. 커스텀 훅의 경우 테스트를 위해
renderHook
,act
를 사용해야 했다. - 마지막으로 비동기 검증의 경우, 타이머 예시를 살펴보았다. 테스트는 동기적으로 작동하기에 일정 시간 후에 함수를 테스트하기 어렵다. 그래서, 비동기 함수를 테스트하기 위해
useFakeTimers
로 타이머를 조작했다. - 이처럼 단위 테스트에는 여러 검증법이 있는데, 더 많은 방법이 있기에 공식문서나 관련 자료를 더 찾아보는 걸 추천한다 🙂
반응형
'이번엔 이 공부 끝내겠다 시리즈 > 테스트코드' 카테고리의 다른 글
[Unit Test] 단위 테스트 개념과 작성시 주의할 점 (0) | 2024.04.21 |
---|
댓글