Test Code

Vitest에서 모의 타이머(mocking timers)로 시간 출력 테스트하기

남희정 2023. 8. 17. 02:55

JavaScript에서 내장객체를 사용하는 퀴즈를 풀게 되었다.

1초에 한 번씩 날짜를 포함한 시간을 출력하는 문제이다. 제시받은 `setInteval()`메서드를 사용하면 된다.

 

setInterval(callback, delay);

// callback : 지정된 시간 간격마다 실행될 콜백 함수
// delay : 콜백 함수가 실행되기 전의 시간 간격을 밀리초 단위로 지정함.

const intervalID = setInterval(whatTime, 1000);

function whatTime() {
  const now = new Date();
  console.log(now);
}

setInterval()

Window 및 Worker 인터페이스에서 제공되는 `setInterval()`메서드는 각 호출 사이에 고정된 시간 지연으로 함수를 반복적으로 호출하거나 코드 스니펫을 실행한다.

 

➡️ 일정한 시간 간격으로 원하는 작업을 반복적으로 수행할 수 있다.

사용 후 `clearInterval()`함수를 호출하여 반복 작업을 중지할 수 있다. 웹 애플리케이션에서 업데이트나 상태 변경을 주기적으로 감지하는 데 유용하게 사용할 수 있음.

// intervalID를 사용하여 반복 작업 중지
clearInterval(intervalID);

 

`console.log()`로 출력하는 것은 쉬웠다.

다만 이걸 테스트 코드로 옮기자니 막막해졌다. 1초마다 시간이 계속 출력되는데 어떻게 테스트하지? 🤔💭

 Vitest로 테스트를 해야하기 때문에 Vitest 공식 홈페이지를 참고했다.

vi.useFakeTimers()

이 메서드는 *모의 타이머(mocking timers)를 활성화하는데 사용된다.

모의 타이머란, 테스트 환경에서 `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`, `nextTick`, `setImmediate`,  `clearImmediate` 및 Date와 같은 타이머와 관련된 함수를 모의화하여 가짜 시간 제어를 할 수 있는 기능이다. 
테스트 시간을 조작하거나 타이머 동작을 제어하여 테스트 시나리오를 만들거나 시간 기반 동작을 테스트할 때 유용하다.

 

즉, `vi.useFakeTimers()`를 호출하면 이후의 타이머 관련 함수 호출은 모의화되어 가짜 시간을 사용하게 된다.
테스트 환경에서 타이머 동작을 실제로 실행하지 않고 시간을 미리 설정하거나 조작하여 테스트를 수행할 수 있게 된다.

 

`setInterval()`은 `clearInterval()`로 초기화해주어야 하는 것 처럼, `vi.useFakeTimers()`는 `vi.useRealTimers()`를 호출하여 원래의 타이머 동작으로 복원할 수 있다. 이전에 실행된 모든 타이머는 복원되지 않는다.

 

vi.?

`vi`는 Vitest 테스트 프레임워크에서 제공하는 기능을 사용하기 위한 객체다. Vitest는 테스트 코드를 작성하고 실행하기 위한 도구로서, `vi` 객체를 통해 다양한 기능을 제어하고 테스트 환경을 설정할 수 있다.

`vi` 객체는 Vitest의 일부로서, 다양한 메서드와 속성을 포함하고 있다. 테스트 코드에서 `import`를 사용하여 가져와서 사용할 수 있다.

 

vi.advanceTimersByTime()

지정한 밀리초(ms) 후에 종료되는 메서드. 이 메서드를 사용하면 테스트에서 시간을 빠르게 흐르게 하거나 조작하여 특정 시간 동안의 동작을 테스트할 수 있다.
예를 들어 다음 코드는 1, 2, 3을 출력하고 예외를 발생시키지 않는다.

let i = 0;
setInterval(() => console.log(++i), 50);

vi.advanceTimersByTime(150);

위 코드는 `setInterval()`을 사용하여 50 밀리초마다 숫자를 증가시키며 출력하는 예제다.

 

풀이 과정

// clock.js

function startClock() {
  const intervalID = setInterval(whatTime, 1000);

  function whatTime() {
    const now = new Date();
    console.log(now);
  }
}

export { startClock };
import { test, expect, vi } from "vitest";
import { startClock } from "../../built-in/clock";

test("prints the current time every second", async () => {
	
    // 모의 타이머 활성화하기
    vi.useFakeTimers();
    
    // console.log 대신 결과를 저장할 배열 사용
    const consoleOutput = [];
    
    // console.log를 재정의하여 출력을 배열에 저장
    console.log = message => {
      consoleOutput.push(message);
    };
    
    // startClock() 함수 호출, setInterval()을 사용하여 주어진 간격마다 시간을 출력하는 작업을 반복.
    // clockPromise는 이 작업이 완료될 때까지 기다리기 위해 사용될 프로미스이다.
    // vi.advanceTimersByTime() 메서드를 호출하여 시간을 1000밀리초(1초)만큼 빠르게 흐르게 함.
    // 이렇게 함으로써 테스트 코드는 시계가 1초에 한 번씩 작동하는지를 테스트할 수 있다.
    
    const clockPromise = startClock();
    vi.advanceTimersByTime(1000);
    
    await clockPromise;
    
    // 예상 출력과 실제 출력을 비교하여 테스트 진행
    expect(consoleOutput).toEqual([
      new Date().toString()
    ]);
    
    // 모의 타이머 복원
    vi.useRealTimers();
});

But. 오류 발생 😥

AssertionError: expected [ 2023-08-16T14:49:09.537Z ] to deeply equal [ Array(1) ]

- Expected
+ Received

  Array [
-   "Wed Aug 16 2023 23:49:09 GMT+0900 (GMT+09:00)",
+   2023-08-16T14:49:09.537Z,
  ]

예상 출력 Expected 과 실제 출력 Received 의 형식이 다르기 때문에 생긴 문제. 

예상 출력은 문자열 형태의 날짜와 시간을 포함하고 있지만, 실제 출력은 JavaScript의 `Date`객체이다.

 

두 형식을 통일시키기 위해 동일한 형태로 변환해야 했다.

import { test, expect, vi } from "vitest";
import { startClock } from "../../built-in/clock";

test("prints the current time every second", async () => {
  vi.useFakeTimers();

  const consoleOutput = [];
  console.log = (message) => {
    consoleOutput.push(message);
  };

  const clockPromise = startClock();
  vi.advanceTimersByTime(1000);

  await clockPromise;
  
  // 예상 출력을 문자열로 변환
  const expectedOutput = [new Date().toString()];  
  // 실제 출력도 문자열로 변환하여 배열 생성
  const receivedOutput = consoleOutput.map((date) => new Date(date).toString()); 
  
  // 예상 출력과 실제 출력을 비교하여 테스트 진행
  expect(receivedOutput).toEqual(expectedOutput);
  
  // 모의 타이머 복원
  vi.useRealTimers();
});

 

내장객체의 경우엔 많이 써보면서 익힐 수 밖에 없다. 테스트 코드와 병행하니 배움의 폭이 하나를 배우더라도 몇 배로 배울 수 있게 된다. 테스트 코드에 `async/await` 가 많이 등장하는데, 빨리 속도를 따라 잡아서 완전한 이해를 하고 포스팅을 해보아야겠다. 

 

 


[vi.advanceTimersByTime]

Dream Coding Academy | 자바스크립트 마스터리 (ES6+)🎥

ChatGPT 🤖