Unit Test | 3.4. 타이머 테스트

Kate Jung·2024년 2월 13일
0

Front-end Test

목록 보기
11/17
post-thumbnail

📌 예시 | debounce 함수

🔹 common.js

// 연이어 호출해도 마지막 호출 기준으로 지정된 타이머 시간이 지난 경우에만 콜백 함수를 호출
// -> 특정 함수의 호출 횟수를 제한하는 기능
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을 사용하여 호출 횟수를 제한

🔹 common.spec.js


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. 1번째 테스트 (기본 기능)

    • Spy 함수

      디바운스 함수의 테스트를 위해서는 콜백 함수를 하나 넘겨 특정 시간이 지났을 때 호출되는지 검증해야 함

      → 함수의 호출 여부를 확인하기 위해서는 Spy 함수를 콜백 함수로 사용

  2. 2번째 테스트 (핵심 기능)

    • 테스트 해석

      4번을 호출했지만 실제 spy 함수는 0.3초 이상이 지난 후에 호출된 마지막 4번째 함수에서만 단 1번 호출됨

    • 매처 비교

      • toHaveBeenCalled 매처

        spy 함수의 호출 여부만 확인

      • toHaveBeenCalledTimes 매처

        함수의 호출 횟수까지 단언

📌 타이머 모킹

🔹 이유

  • 원하는 시간만큼 딜레이를 해야만 정상적으로 검증 가능

    기본적으로 테스트 코드는 동기적으로 실행됨 (비동기 타이머와 무관)

    ⇒ 즉, 비동기 함수가 실행되기 전에 단언이 실행되어 의도대로 동작 안함.

  • 원하는 시점에 n초가 지난 것처럼 상황을 만드는 방법

    테스트 프레임워크에서 타이머를 모킹하여 원하는 대로 제어할 수 있는 API를 제공

🔹 방법

🔸 useFakeTimers

테스트 실행 전 호출

🔸 beforeEach 셋업 함수

  • 사용 이유

    디바운스 함수의 모든 기능은 타이머에 의존

    vi.useFakeTimers 함수를 항상 호출하여 타이머 모킹을 해야 함. 이런 경우, describe 블럭 내에 beforeEach setup 함수를 하나 지정하여 테스트 실행 전 항상 타이머를 모킹하도록 설정

  • 장점

    debounce describe 스코프 내에서만 실행

    → 스코프 밖의 다른 테스트가 실행될 때 불필요하게 실행되지 않음

🔸 advanceTimersByTime

원하는 ms만큼 시간이 지난 것으로 타이머를 조작

🔸 afterEach Teardown에서 모킹 초기화하기

테스트 실행 전 특정 모듈에 대한 모킹을 한 경우 테스트가 끝난 후에 Teardown에서 모킹 초기화를 해야 함

  • 꼭 기억해야 할 중요한 사항

  • 이유

    다른 테스트에 영향을 미치지 않고 안정적으로 테스트를 실행 가능

  • 방법

    • useRealTimers API 활용

      타이머를 원상태로 복구

  • 초기화하지 않았을 경우

    앱 내부의 3rd 파티 라이브러리 or 전역의 teardown에서 타이머에 의존하는 로직이 있다면 fakeTimer로 인해 제대로 동작하지 않을 수 있음.

    → 이런 문제 방지

🔸 setSystemTime

  • 테스트가 실행되는 현재 시간을 정의

  • 사용 방법

    useFakeTimers API를 호출 후, setSystemTime에 원하는 날짜를 나타내는 객체 or 값 넣기

  • 사용 이유

    • 시간을 고정하면 일관된 환경에서 테스트 가능

    • 현재 시간을 활용하는 함수의 테스트를 실행한다고 가정한다면

      시간은 계속 흐르기 때문에 매일매일 시스템 시간도 달라짐. 이 경우 테스트 당시의 시간에 의존하는 테스트가 있을 때 시간을 고정하지 않으면 시간의 흐름에 따라 테스트 역시 깨질 수 있음.

📌 정리


참고

  • 실무에 바로 적용하는 프런트엔드 테스트
profile
복습 목적 블로그 입니다.

0개의 댓글