// 연이어 호출해도 마지막 호출 기준으로 지정된 타이머 시간이 지난 경우에만 콜백 함수를 호출
// -> 특정 함수의 호출 횟수를 제한하는 기능
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);
};
};
사용하는 경우
대량의 이벤트 핸들러(ex. 스크롤)가 발생할 때 성능 개선 목적
디바운스 함수의 구현
특정 시간에 따라 함수의 호출 여부를 판단하기 위해 내부적으로 타이머 SetTimeout과 ClearTimeout을 사용하여 호출 횟수를 제한
describe('debounce', () => {
beforeEach(() => {
vi.useFakeTimers(); // 👈
// 2023년 12월 25일에 테스트를 하는 환경 형성
vi.setSystemTime(new Date('2023-12-25')); // 👈
});
afterEach(() => {
vi.useRealTimers(); // 👈
});
// 1️⃣ 기본 기능
it('특정 시간이 지난 후 함수가 호출된다.', () => {
vi.useFakeTimers();
const spy = vi.fn();
const debouncedFn = debounce(spy, 300);
debouncedFn();
vi.advanceTimersByTime(300); // 👈
expect(spy).toHaveBeenCalled();
});
// 2️⃣ 핵심 기능
it('연이어 호출해도 마지막 호출 기준으로 지정된 타이머 시간이 지난 경우에만 함수가 호출된다.', () => {
const spy = vi.fn();
const debouncedFn = debounce(spy, 300);
// 최초 호출
debouncedFn();
// 최초 호출 후 0.2초 후 호출
vi.advanceTimersByTime(200);
debouncedFn();
// 두번째 호출 후 0.1초 후 호출
vi.advanceTimersByTime(100);
debouncedFn();
// 세번째 호출 후 0.2초 후 호출
vi.advanceTimersByTime(200);
debouncedFn();
vi.advanceTimersByTime(300);
expect(spy).toHaveBeenCalledTimes(1); // 👈
});
});
1번째 테스트 (기본 기능)
Spy 함수
디바운스 함수의 테스트를 위해서는 콜백 함수를 하나 넘겨 특정 시간이 지났을 때 호출되는지 검증해야 함
→ 함수의 호출 여부를 확인하기 위해서는 Spy 함수를 콜백 함수로 사용
2번째 테스트 (핵심 기능)
테스트 해석
4번을 호출했지만 실제 spy 함수는 0.3초 이상이 지난 후에 호출된 마지막 4번째 함수에서만 단 1번 호출됨
매처 비교
toHaveBeenCalled
매처
spy 함수의 호출 여부만 확인
toHaveBeenCalledTimes
매처
함수의 호출 횟수까지 단언
원하는 시간만큼 딜레이를 해야만 정상적으로 검증 가능
기본적으로 테스트 코드는 동기적으로 실행됨 (비동기 타이머와 무관)
⇒ 즉, 비동기 함수가 실행되기 전에 단언이 실행되어 의도대로 동작 안함.
원하는 시점에 n초가 지난 것처럼 상황을 만드는 방법
테스트 프레임워크에서 타이머를 모킹하여 원하는 대로 제어할 수 있는 API를 제공
테스트 실행 전 호출
사용 이유
디바운스 함수의 모든 기능은 타이머에 의존
→ vi.useFakeTimers
함수를 항상 호출하여 타이머 모킹을 해야 함. 이런 경우, describe 블럭 내에 beforeEach setup 함수를 하나 지정하여 테스트 실행 전 항상 타이머를 모킹하도록 설정
장점
debounce describe 스코프 내에서만 실행
→ 스코프 밖의 다른 테스트가 실행될 때 불필요하게 실행되지 않음
원하는 ms만큼 시간이 지난 것으로 타이머를 조작
테스트 실행 전 특정 모듈에 대한 모킹을 한 경우 테스트가 끝난 후에 Teardown에서 모킹 초기화를 해야 함
꼭 기억해야 할 중요한 사항
이유
다른 테스트에 영향을 미치지 않고 안정적으로 테스트를 실행 가능
방법
useRealTimers API 활용
타이머를 원상태로 복구
초기화하지 않았을 경우
앱 내부의 3rd 파티 라이브러리 or 전역의 teardown에서 타이머에 의존하는 로직이 있다면 fakeTimer로 인해 제대로 동작하지 않을 수 있음.
→ 이런 문제 방지
테스트가 실행되는 현재 시간을 정의
사용 방법
useFakeTimers
API를 호출 후, setSystemTime에 원하는 날짜를 나타내는 객체 or 값 넣기
사용 이유
시간을 고정하면 일관된 환경에서 테스트 가능
현재 시간을 활용하는 함수의 테스트를 실행한다고 가정한다면
시간은 계속 흐르기 때문에 매일매일 시스템 시간도 달라짐. 이 경우 테스트 당시의 시간에 의존하는 테스트가 있을 때 시간을 고정하지 않으면 시간의 흐름에 따라 테스트 역시 깨질 수 있음.
참고