🤔

【React vitest テストコード】カスタムhooksのテストでメモリが制限に達してしまい失敗した😇

2024/08/07に公開

はじめに

こんにちは。テストコード書くのに時間をかけすぎたてるし〜です。
ということで、今日はテストコードで突っかかったことについての記事を書いていこうと思います。
github copilotを使って解決しようとしましたが、まともな回答が出ずググったら一発で、解決しました(なんてこったパンナコッタ🤪)。

今回はメモとして残しておくためのものになるので雑な記事になってしまうかもですが、最後まで読んでいってもらえると嬉しいです。

それではLet's go!!!

起こった現象

仕事でカスタムhookのテストコードを書いていました。
下記のようなテストコードをカキカキ...🧑🏻‍💻

import { renderHook } from '@testing-library/react'
import { describe, vi, it } from 'vitest'

describe('useCustomHook',()=>{
    it('test1',()=>{
        //モックのデータ等の定義...
        const { result } = renderHook(()=>
            useCustomHook({
                id: 123,
                name: "hogehoge~~"
            })
        )
    })
    //テストコードの続き...(以下略
})

「よっしゃ〜!書き終わった!実行!(pnpm testポチッとな💣)」

Unhandled Errors 

Vitest caught 1 unhandled error during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.

Unhandled Error 
Error: Worker terminated due to reaching memory limit: JS heap out of memory
 ❯ [kOnExit] node:internal/worker:313:26
 ❯ Worker.<computed>.onexit node:internal/worker:229:20

「な〜〜〜〜〜んだと〜〜〜〜〜〜😇🫠」

エラーをDeepLにて訳すと

Vitestはテスト実行中に1つの未処理エラーを検出した。
これは偽陽性のテストを引き起こす可能性があります。ハンドリングされていないエラーを解決して、テストに影響がないことを確認してください。

エラー: メモリ制限に達したため、ワーカーが終了しました。

メモリが制限に達してしまいテストが実施できなかったというエラーです。
解決方法がわかった人なら違和感があると思いますが、わからない人にとっては何がおかしいのか?となるはずです。

実際に自分も「???」になりました。当時の私は、

見た事ないエラーにパニックになりかける私

よ〜し!頼んだcopilotちゃん
↓ 2時間後
「も〜〜〜無理、、直らん、、、」

「OK!Google!....(以下略」

isseu発見。
↓ 5分後
解決

という感じでした😇

解決法

先程のテストコードを下記のように書き換えます。

import { renderHook } from '@testing-library/react'
import { describe, vi, it } from 'vitest'

describe('useCustomHook',()=>{
    it('test1',()=>{
        //モックのデータ等の定義...
+      const params={
+            id: 123,
+            name: "hogehoge~~",
+       } 
        const { result } = renderHook(()=>
-            useCustomHook({
-                id: 123,
-                name: "hogehoge~~"
-            })
-        )
+        useCustomHook(params)
    })
    //テストコードの続き...(以下略
})

これでテストが通ります。

解決に至ったissue

https://github.com/testing-library/react-hooks-testing-library/issues/413

中身を実際に読んでいきます。

issueを作成した人の質問内容

  • 先程私が起こったというエラーと同じものが起こった
  • カスタムhooksとテストコードを記載
  • 引数をuseMemoでラップしたけど無理だったとのこと

それに対する回答

  • hooksがレンダーされる度に引数であるオブジェクトが生成されてしまう
  • 実際のフローを説明
  • 1番簡単な修正はrenderHookのコールバック(恐らく引数)を外に宣言する
  • この解決方法はhooksの結果を返すものだが新しい引数が渡されたときに何が起こるかをテストしたい場合のコードを下に記載

実際のフローは以下のようになっています。

  1. renderHook(...)
  2. useTest({ arr: [] }) (new reference)
  3. useEffect(..., [init]) (initial render, effect triggered)
  4. setArr(Array.from(init.arr)) (state changed, render triggered)
  5. renderHook(...)
  6. useTest({ arr: [] }) (new reference)
  7. useEffect(..., [init]) (dependency changed, effect triggered)
  8. setArr(Array.from(init.arr)) (state changed, render triggered)
  9. renderHook(...)
  10. useTest({ arr: [] }) (new reference)
  11. useEffect(..., [init]) (dependency changed, effect triggered)
  12. setArr(Array.from(init.arr)) (state changed, render triggered)
  13. renderHook(...)
    ... and we're looping

上記回答により解決した模様。
また、私自身のテストコードも書き換えたらテストが通った🎉

私の考察

考察といってもほぼ、上記に書いてあることなのだが、hooksは状態が変わるたびにレンダーされる。その際に引数にそのまま直で宣言してしまうとメモリの番地が変わって処理が重くなってしまう。なので外に宣言しその宣言したものを渡すことで、番地が変わることがないので処理が軽くなる。

某OSSでもそのような作りをしていたことを勉強したのに応用できなかった(ばかやろ〜〜〜〜〜😇)

まとめ

今回はテストで突っかかったことを記事にしました。
もしかしたら、「あ〜〜そんなことあったな〜」という方もいらっしゃるのではないのではないでしょうか。

解決に時間がかかってしまい苦労したので一生忘れなさそうです🤪

同じ問題に境遇した時に解決方法の手段になっていただけると嬉しいです(あるかどうかは知りませんが)。

それではまた👋

Discussion