Vue 3.0/03.SFC와 Vite

[vue 3] 07. vitest를 이용한 단위 테스팅 4

  • -

이번 포스트에서는 setTimeout 이나 setInterval 사용 환경 즉 Timer에 대한 테스팅에 대해 살펴보자.

Timer Test

 

테스트 대상 함수 준비

먼저 테스트 할 함수를 준비해보자. 전체적인 테스트는 sync와 async 환경에서 테스트 한다.

let interval500 = 0,  timeout2000 = 0 // 동작 회수를 카운트하기 위한 변수

let intervalId                        // interval 작업을 중지하기 위한 변수
const mywork = () => {
  setTimeout(() => {
    console.log('timeout 2000', timeout2000++)
  }, 2000)

  intervalId = setInterval(() => {
    console.log('interval 500', interval500++)
  }, 500)
}

편의상 2000ms 후 동작하는 timeout 작업은 timeout2000, 500ms 마다 동작하는 interval 작업은 interval500이라고 부르자.

위의 mywork에 대한 async 버전도 아래처럼 준비해보자.

const myworkAsync = () => {
  setTimeout(() => {
    Promise.resolve().then(() => console.log('async timeout 2000', timeout2000++))
  }, 2000)

  intervalId = setInterval(() => {
    if (interval500 > 10) {
      clearInterval(intervalId)
    } else {
      Promise.resolve().then(() => console.log('async interval 500', interval500++))
    }
  }, 500)
}

sync 버전과는 달리 Promise를 활용하며 interval 작업에서 interval500이 10보다 커지면 작업을 중지시켰는데 이는 무한 interval 테스트 시 error가 발생하는데 이상하게 assertion이 되지 않아서 궁여지책으로 infinite 상황을 제외하기 위한 조치이다.

 

테스트 준비

timer에 대해 임의로 제어해야 하기 때문에 모든 테스트 작업에서 fake timer를 사용하도록 설정해야 한다.

// fake time에서 처리
beforeEach(() => {
  vi.useFakeTimers()
  interval500 = 0
  timeout2000 = 0
})

afterEach(() => clearInterval(intervalId))

 

테스팅

 

다음은 vitest에서 제공하는 다양한 테스트 utility의 사용 예이다.

주어진 시간까지 타이머 동작

advanceTimersByTime와 advanceTimersByTimeAsync는 각각 동기와 비동기 Timer에 대해서 주어진 시간이 지난 후의 상태를 점검할 수 있게 도와준다.
정확히 3초가 지난 후 timeout2000은 1번 동작하고 interval500은 6번 동작했을 것이다.
test('주어진 시간까지 타이머 동작(동기)', () => {
  mywork()
  vi.advanceTimersByTime(3000)
  expect(interval500).toBe(6)
  expect(timeout2000).toBe(1)
})

test('주어진 시간까지 타이머 동작(비동기)', async () => {
  myworkAsync()
  await vi.advanceTimersByTimeAsync(3000)
  expect(interval500).toBe(6)
  expect(timeout2000).toBe(1)
})

 

시간 순으로 다음 타이머 동작

advanceTimersToNextTimer와 advanceTimersToNextTimerAsync는 각각 동기와 비동기 Timer에 대해서 Timer Queue의 내용을 하나씩 순서대로 실행해준다. 
예를 들어 4번의 nextTimer를 호출한다면 시간 순으로 interval500 > interval500 > interval500 > interval500 & timeout2000이 동작한다.
test('시간 순으로 다음 타이머 동작 실행(동기)', () => {
  mywork()
    vi.advanceTimersToNextTimer().advanceTimersToNextTimer()
      .advanceTimersToNextTimer().advanceTimersToNextTimer()
      
    expect(interval500).toBe(4)
    expect(timeout2000).toBe(1)
})

test('시간 순으로 다음 타이머 동작 실행(비동기)', async () => {
  myworkAsync()
  for (let i = 0; i < 4; i++) {
    // await로 호출해야 하는데 chaining 안되나?
    await vi.advanceTimersToNextTimerAsync()
  }
  
  expect(interval500).toBe(4)
  expect(timeout2000).toBe(1)
})

advanceTimersToNextTimer는 chaining 방식으로 호출되는데 advanceTimersToNextTimerAsync는 안되는것 같다. await 때문인가? 

 

모든 대기중인 타이머가 끝날 때까지 진행

runAllTimers와 runAllTimersAsync는 각각 동기와 비동기 상황에서 모든 타이머가 끝날때 까지 진행한다. 단 무한 타이머의 경우 10,000번까지 시도하고 예외를 발생시킨다.
test('모든 대기중인 타이머가 끝날때 까지 진행. 무한대의 경우 10000 번까지만하고 실패(동기)', () => {
  mywork()
  expect(() => vi.runAllTimers()).toThrowError(
    'Aborting after running 10000 timers, assuming an infinite loop!'
  )
})

test('모든 대기중인 타이머가 끝날때 까지 진행. 무한대의 경우 10000 번까지만(비동기)', async () => {
  myworkAsync()
  await vi.runAllTimersAsync()
  
  expect(interval500).toBe(11)
  expect(timeout2000).toBe(1)
})

그런데 runAllTimers의 경우 예외가 잘 검출이 되었는데 runAllTimersAsync의 경우는 Function이 반환되었다. 그래서 myworkAsync에서는 10보다 크면 작업을 중지시켜  버렸다. 뭔가 또 놓친것이 있나 ㅜㅜ 예외 검증이 잘 되시는 분 알려주세요. ~~

 

모든 대기중인 타이머가 1번은 끝날때 까지만 진행

timeout2000이 끝나려면 최소 2000ms가 필요하다 이 시간이면 interval500은 4번 진행될 것이다. runOnlyPendingTimers나runOnlyPendingTimersAsync는 각각 동기/비동기에서 모든 타이머들이 최소 한번은 진행한 후에 종료한다.

test('모든 대기중인 타이머가 1번 끝날때 까지만 진행 - 가장 큰 시간(동기)', () => {
  mywork()
  vi.runOnlyPendingTimers()
  
  expect(interval500).toBe(4)
  expect(timeout2000).toBe(1)
})

test('모든 대기중인 타이머가 1번 끝날때 까지만 진행 - 가장 큰 시간(비동기)', async () => {
  myworkAsync()
  await vi.runOnlyPendingTimersAsync()

  expect(interval500).toBe(4)
  expect(timeout2000).toBe(1)
})

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.