API リクエストのモック不足で Jest が終了しなくなった失敗談

2023/10/23に公開

ライフイズテック株式会社 サービス開発部の山口 (@no_clock) です。塾プロダクトグループでソフトウェアエンジニアをしています。

プロダクトの開発と並行しながら、フロントエンドのテストコードを増やしています。テストカバレッジは 1 ヶ月前は 25% ほどでしたが、現在は 38% まで上がっています。

今回は、テストを増やしていたときの失敗エピソードです。

3 行でまとめると

  • Next.js + SWR と Jest + MSW (Mock Service Worker) の構成でテストコードを書いていた
  • あるときから Jest が終了しなくなったが、欠けていた API リクエストのモックを記述すると直った
  • (おそらく) SWR が律儀にリトライしてくれていた様子

おもな構成

事の発端: Jest が終了しなくなる

あるときから、 テスト完了後に Jest did not exit one second after the test run has completed. と表示され、 1 分ほど Jest が終了しなくなりました。

$ npm run test
()
Test Suites: 17 passed, 17 total
Tests:       3 skipped, 92 passed, 95 total
Snapshots:   0 total
Time:        17.174 s
Ran all test suites.
Jest did not exit one second after the test run has completed.

'This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

1 分ほど経過すると、以下のメッセージとともに Jest が終了しました。

/Users/foo/bar/node_modules/swr/_internal/dist/index.js:140
    const visibilityState = isDocumentDefined && document.visibilityState;
                                                          ^

TypeError: Cannot read properties of undefined (reading 'visibilityState')
    at Object.isVisible (/Users/foo/bar/node_modules/swr/_internal/dist/index.js:140:59)
    at isActive (/Users/foo/bar/node_modules/swr/core/dist/index.js:55:38)
    at /Users/foo/bar/node_modules/swr/core/dist/index.js:330:29
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

--detectOpenHanles オプションをつけたいところですが、それよりも TypeError のスタックトレースが swr (SWR) ライブラリ付近で出ているのが気になりました。

仮説: テスト完了後に SWR が動き続けている?

スタックトレースに従って vercel/swr のソースコードを辿ってみると、エラー発生時のリトライ処理でした。

            if (
              shouldRetryOnError === true ||
              (isFunction(shouldRetryOnError) &&
                shouldRetryOnError(err as Error))
            ) {
              if (isActive()) {

https://github.com/vercel/swr/blob/v2.2.4/core/src/use-swr.ts#L493-L498

SWR のリトライ処理が動き続けている可能性が高そうです。

さらに仮説: モックしていない API リクエストを延々リトライしている?

思い当たる節がありました。検証に必要な API リクエスト以外は、モックしていませんでした。警告メッセージは表示されていましたが、検証には支障がないと考えて無視していました。

  console.warn
    [MSW] Warning: captured a request without a matching request handler:
    
      • GET http://localhost:3000/foo/bar
    
    If you still wish to intercept this unhandled request, please create a request handler for it.
    Read more: https://mswjs.io/docs/getting-started/mocks

      at Object.warn (node_modules/msw/src/utils/internal/devUtils.ts:17:11)
      at applyStrategy (node_modules/msw/src/utils/request/onUnhandledRequest.ts:208:18)
      at onUnhandledRequest (node_modules/msw/src/utils/request/onUnhandledRequest.ts:233:3)
      at handleRequest (node_modules/msw/src/utils/handleRequest.ts:80:5)
      at node_modules/msw/src/node/SetupServerApi.ts:69:24

MSW (Mock Service Worker) はモックしていないリクエストはスルーするため、実際にリクエストが発行されます。しかし、 API サーバーを実行していないため当然リクエストは失敗します。

このリクエストをリトライし続けていると考えると、現象の説明がつきそうです。

解決: 原因はモック不足

API リクエストのモックを追加すると、 Jest はテスト完了後すぐに終了してくれるようになりました。

コードリーディングが不完全なので確証はありませんが、仮説は当たっていそうです。

$ npm run test
()
Test Suites: 17 passed, 17 total
Tests:       3 skipped, 92 passed, 95 total
Snapshots:   0 total
Time:        14.593 s
Ran all test suites.

$ 

ちょっと宣伝

ライフイズテック サービス開発部では、気軽にご参加いただけるカジュアルなイベントを実施しています。開催予定のイベントは、 connpass のライフイズテックグループからご確認ください!

Discussion