jest.useFakeTimers()を使ってテスト時間を短縮する

ある外部SDKをラップしたものをjestでテストしたい。 外部のSDKの仕様も相まって、ちょっとややこしかった。

jest.useFakeTimers() を使ったテストが初めてだったのもあってメモ。

※以下サンプルソースですが、雰囲気コードなので動作しないかも。

SampleSdkという外部のモジュールがあるとする。

// SampleSdk.d.ts

interface initContext {
  type: 'init',
  body: { isMobile: boolean }
}

interface getSessionContext {
  type: 'getSession',
  body: {
    token: string
  }
}

type Context = initContext | getSessionContext

interface Window {
  // Windowオブジェクトの中にSampleSDKオブジェクトが入っている。
  SampleSDK:  {
    dispatch: (context: Context) => void
    emit: (type: 'init' | 'getSession') => void
  }
}

それをこんな感じでラップする。

// sample.ts

class SampleSdk {
  window: Window
  emitter: EventTarget
  promise: null | Promise<initContext['body']> = null

  constructor(window: Window) {
    this.window = window
    // emitterはmittでもなんでもよい、雰囲気で。
    this.emitter = window.EventTarget

    this.init()
  }

  async init():  Promise<initContext['body']> {
    if (this.promise) {
      return this.promise
    }

    const initEvent = new Event('init'),
    this.window.SampleSDK.dispatch = (context) => {
      if (context.type === 'init') {
        this.emitter.dispatchEvent(events[context.type], context.body)
      }
    })

    this.promise = new Promise(resolve => 
      this.emitter.addEventListener('init', resolve)
    )

    this.window.SampleSDK.emit('init')

    return this.promise
  }
}

// 実際に使うときは
// const sdk = new SampleSdk(window)
// const body = await sdk.init()
// console.log(body) // { isMobile: false }

await SampleSdk.init()で isMobile が取得できるかテストする

// sampleSdk.spec.ts

import SampleSdk from './sampleSdk'

describe('SampleSDK', () => {
  it('初期化完了後デバイス情報が返却されること', () => {
    const mockWindow = {
        SampleSdk: {
            dispatch: () => jest.fn(),
            emit() {
              window.setTimeout(
                  () => this.SampleSdk.dispatch({ type: 'init', body: {isMobile: false}}),
                  1000
              );
            }
        };
    }

    new SampleSDk(mockWindow);
    expect(await this.init()).toEqual({ isMobile: false })
  })
})

このままでも一応動くんだけど、テストが完了するまでに絶対1秒かかる。 ので、 jest.useFakeTimers()jest.runAllTimers(); を組み合わせることで、 テスト時間を短縮できる。

 // sampleSdk.spec.ts
 
 import SampleSdk from './sampleSdk'
 
 describe('SampleSDK', () => {
   it('初期化完了後デバイス情報が返却されること', () => {
+    jest.useFakeTimers();
     const mockWindow = {
         SampleSdk: {
             dispatch: () => jest.fn(),
             emit() {
               window.setTimeout(
                   () => this.SampleSdk.dispatch({ type: 'init', body: {isMobile: false}}),
                   1000
               );
             }
         };
     }

     new SampleSDk(mockWindow);
+    jest.runAllTimers();
     expect(await this.init()).toEqual({ isMobile: false })
   })
 })