jest.getTimeCount()는 clearTimeout도 카운팅한다.

Eye0n·2022년 5월 9일
0

이 글을 적게된 이유는 해당 함수를 테스트하면서 내 예상과는 다른 결과를 보여줬기에 이를 이해하고 해결하면서 알게된 것들을 기록하고자 함이다.

jest에서 time count 시 setTimeout 뿐만이 아니라 clearTimeout도 카운트가 되기 때문에 발생한 오류를 해결한 과정을 기술한 글이다.

먼저 해당 테스트는 react hook을 테스트하기 위함이며 jest@testing-library/react-hooks을 사용한다.

jest환경에서 time관련 테스트를 할 시 jest가 제공하는 Fake Timer을 써야한다.
https://jestjs.io/docs/jest-object#fake-timers
위의 링크에서 Fake Timers의 정의에서 아래와 같이 설명되어 있다.

Fake timers will swap out Dateperformance.now()queueMicrotask()
setImmediate()clearImmediate()setInterval()clearInterval()
setTimeout()clearTimeout()
with an implementation that gets its time from the fake clock.

시간과 관련된 함수에 대해 모두 fake timer로 대체한다.

fake timer에서 남아 있는 시간함수를 카운트 해주는 함수가 있는데 바로 jest.getTimerCount() 이다.

jest.getTimerCount()

Returns the number of fake timers still left to run.

⇒ faker timers의 jest.useFakeTimers()jest.getTimerCount() 를 가지고 해당 함수의 테스트를 작성했다.

하지만

useDebounce가 실행되어 1개를 리턴해주기를 예상했으나 2개가 리턴되었다.

useDebounceValue code

import { useRef, useEffect, useState } from 'react';

const useDebounceValue = (value, threshold = 100) => {
  const timerRef = useRef(null);
  const [debounceValue, setDebouceValue] = useState(null);

  useEffect(() => {
    timerRef.current = setTimeout(() => {
      setDebouceValue(value);
    }, threshold);

    return () => clearTimeout(timerRef.current);
  }, [value, threshold]);

  return debounceValue;
};

export default useDebounceValue;

test code

it('should return the value using default time without threshold time argument', () => {
    const defaultTime = 100;
    const mockValue = 'test';
    const { result } = renderHook(
      ({ mockValue }) => useDebounceValue(mockValue),
      {
        initialProps: { mockValue },
      }
    );
	
	// expect(received).toBe(expected)
	// expect: 1, received: 2
    expect(jest.getTimerCount()).toBe(1); // 1개가 될거라 예상했지만 2개 왜????

    act(() => {
      jest.advanceTimersByTime(defaultTime);
    });

    expect(result.current).toBe(mockValue);
    expect(jest.getTimerCount()).toBe(0);
  });

expect(jest.getTimerCount()).toBe(1); 여기 assertion이 통과가 안된다.

useDebounceValue의 useEffect에서 setTimeout을 걸어줬으니 당연히 1개가 될거라고 생각했다.

why???

해당 assertion을 통과하기 위해 했던 시도한 방법들

  1. jest.advanceTimersByTime(1);
  2. useDebouonceValue의 useEffect에서 cleanUp만 제거
  3. useDebouonceValue의 useEffect에서cleanUp만 실행
  1. jest.advanceTimersByTime(1)
it('should return the value using default time without threshold time argument ', () => {
    const defaultTime = 100;
    const mockValue = 'test';
    const { result } = renderHook((mockValue) => useDebounceValue(mockValue), {
      initialProps: mockValue,
    });

    jest.advanceTimersByTime(1); // **추가된 코드**

    expect(jest.getTimerCount()).toBe(1); // **실패 지점에서 성공으로 통과**

    act(() => {
      jest.advanceTimersByTime(defaultTime);
    });

    expect(result.current).toBe(mockValue);
    expect(jest.getTimerCount()).toBe(0);
  });

Success는 했지만 성공으로 된 이유를 모르는 상태.....

  1. 기존 test code는 유지한 체 useDebounceValue의 useEffect에서 cleanUp만 제거
    여전히 fail

  2. 기존 test code는 유지한 체 useDebouonceValue의 useEffect에서cleanUp만 실행

it('should return the value using default time without threshold time argument ', () => {
    const defaultTime = 100;
    const mockValue = 'test';
    const { result } = renderHook((mockValue) => useDebounceValue(mockValue), {
      initialProps: mockValue,
    });

    expect(jest.getTimerCount()).toBe(1); // 1지점 **실패 지점에서 성공으로 통과**

    act(() => {
      jest.advanceTimersByTime(defaultTime);
    });

    expect(result.current).toBe(mockValue); // 2지점 새로운 실패 지점
    expect(jest.getTimerCount()).toBe(0);
  });
  • 1지점 fail → success
  • 2지점 새로운 실패 지점 → useDebounceValue의 useEffect에서 cleanUp만 해줬기 때문에 당연히 fail이 되는게 맞음.

1번째 지점에서 성공으로 통과된 부분에서 알게된 사실은 jest.getTimerCount() 가 clearTimeout도 카운팅한다는 점이다.

jest.getTimerCount의 정의에서 left to run 을 해석을 할때 clearTimout을 간과하고 이해했기에 발생한 이슈였다.

그러면 실패 시 시도한 방법 1번은 어떻게 통과했나?

jest.advanceTimersByTime(1); 코드를 실행하므로서
setTimeout이 실행되었고 그 결과 clearTimeout만 남은 상태가 되었기 때문에 expect(jest.getTimerCount()).toBe(1); assertion이 통과되었다 라고 추측한다.


공부하면서 적은 글이기에 틀린 부분이 있을 수 도 있습니다.
틀린 부분은 댓글로 알려주시면 감사하겠습니다.
물론 결론이 추측성으로 끝나 찝찝한 부분이 있을 수 있습니다. 이 부분에 대해 잘 아시는 분이 계시면 알려주세요 감사한 마음으로 배우겠습니다.

0개의 댓글