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);
};
};
일정 시간이 흐른 뒤에 fn 함수를 호출하는 구조로 이루어져있다. 단, 여러번 호출을 한다고 해도 가장 마지막 호출을 기준으로 지정된 타이머 시간이 지나고 한번만 호출될 수 있게 설정했다.
만약 테스트를 진행한다면 2가지로 나눌 수 있다.
debounce는 비동기 함수인 setTimeOut을 사용하기 때문에 spy 함수를 선언해서 호출여부를 파악해도 실패한다. 이는 테스트 로직이 동기이기 때문이며 이를 위해서는 useFakeTimers를 setup 해줘야한다.
beforeEach(() => {
vi.useFakeTimers();
it('특정 시간이 지난 후 함수가 호출된다.', () => {
const spy = vi.fn();
const debouncedFn = debounce(spy, 300);
debouncedFn();
vi.advanceTimersByTime(300);
expect(spy).toHaveBeenCalled();
});
그리고 useFakeTimers를 사용했다면 테스트 구문에서 advanceTimersByTime을 통해 일정 시간 뒤에 호출할수 있도록 테스트를 진행할 수 있다.
it('연이어 호출해도 마지막 호출 기준으로 지정된 타이머 시간이 지난경우에만 함수가 호출된다.', () => {
const spy = vi.fn();
const debouncedFn = debounce(spy, 300);
debouncedFn();
vi.advanceTimersByTime(200);
debouncedFn();
vi.advanceTimersByTime(100);
debouncedFn();
vi.advanceTimersByTime(200);
debouncedFn();
vi.advanceTimersByTime(300);
debouncedFn();
expect(spy).toHaveBeenCalledTimes(1);
});
연이어 호출했어도 가장 마지막에 호축한것을 기준으로 1번만 동작해야하므로, 여러번 debouncedFn을 호출해서toHaveBeenCalledTimes를 1로 설정하여 1번만 호출되는지 테스트해보면 된다.
테스트를 진행한 다음 타이머 모킹을 초기화하는 것도 중요하다. 서드 파티 라이브러리, 전역의 teardown에서 타이머에 의존하는 다른 로직이 있다면 fakeTimer로 인해서 제대로 동작하지 않을수 있기 때문에
afterEach(() => {
vi.useRealTimers();
});
이런식으로 초기회를 진행해주자