deno testコマンドには、test sanitizerという機能があります。この機能は
- Promiseの待ち忘れ
- ファイルやWebSocketの閉じ忘れ
などをチェックしてくれる機能です。
このチェックに引っかかると「Test case is leaking async ops.」や「Test case is leaking 1 resource:」「Test case attempted to exit with exit code: 0」と怒られます。
それぞれのエラーの意味
- 「Test case is leaking async ops.」:Promiseを全てawaitしていない(テスト終了時にresolveされていないPromiseが残っている)
- 「Test case is leaking 1 resource」:ファイルやWebSocketなど、closeしなければいけないものをcloseしていない
- 「Test case attempted to exit with exit code: 0」:テスト中に
Deno.exit()
が呼び出されたため、後続のDeno.test()
が実行されない
「Test case is leaking async ops.」の対処法
「Test case is leaking async ops.」はPromiseの待ち忘れがあるときに発生します。
import { delay } from "https://deno.land/std@0.133.0/async/mod.ts";
Deno.test(function test() {
/* await */ delay(100); // ここでawait していないためエラー
});
> deno test ./test.ts
running 1 test from file:///C:/Users/azusa/work/deno/test/test.ts
test test ... FAILED (20ms)
failures:
test
Test case is leaking async ops.
- 1 async operation to sleep for a duration was started in this test, but never completed. This is often caus
ed by not cancelling a `setTimeout` or `setInterval` call.
To get more details where ops were leaked, run again with --trace-ops flag.
failures:
test
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (60ms)
error: Test failed
例は単純なコードでしたが、大きいコードベースではどのPromiseに対してawaitを忘れているのかが一目では分からないことがあります。
そういう時は --trace-ops
フラグを付けて再実行することで、どのPromiseを待ち忘れたかを知ることができます。
> deno test --trace-ops ./test.ts
running 1 test from file:///C:/Users/azusa/work/deno/test/test.ts
test test ... FAILED (30ms)
failures:
test
Test case is leaking async ops.
- 1 async operation to sleep for a duration was started in this test, but never completed. This is often caus
ed by not cancelling a `setTimeout` or `setInterval` call. The operation was started here:
at Object.opAsync (deno:core/01_core.js:164:42)
at runAfterTimeout (deno:ext/web/02_timers.js:215:31)
at initializeTimer (deno:ext/web/02_timers.js:181:5)
at setTimeout (deno:ext/web/02_timers.js:318:12)
at https://deno.land/std@0.133.0/async/delay.ts:23:15
at new Promise (<anonymous>)
at delay (https://deno.land/std@0.133.0/async/delay.ts:14:10)
at test (file:///C:/Users/azusa/work/deno/test/test.ts:4:3)
at testStepSanitizer (deno:runtime/js/40_testing.js:444:13)
at asyncOpSanitizer (deno:runtime/js/40_testing.js:145:15)
failures:
test
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (215ms)
error: Test failed
見るべき場所はスタックトレースで、下から2行目にあるfile:///始まりのパスがあなたのコードに関する部分です。ここに表示されているファイル名と行数を辿ると、awaitしていないPromiseがあります。
--trace-ops
オプションを付けると詳しいエラーが表示される分、実行速度が遅くなるようです。CIなどでは--trace-ops
オプションを付けずに実行して置き、エラーが出た場合に限り--trace-ops
オプションを付けるとよいでしょう。
修正できない場合の回避策
このエラーが出る原因が別ライブラリにあり、修正が難しい場合、Deno.test()
にオプションを渡してこのエラーを無視することができます。
Deno.test({
name: "test",
fn() {
Deno.open("./a.ts");
},
// sanitizeOps: false, // 「leaking async ops」 を無視
// sanitizeResources: false, // 「leaking resource」を無視
// sanitizeExit: false, // 「exit with exit code: 0」を無視
});
sanitizeOps
/sanitizeResources
/sanitizeExit
それぞれのオプションをfalse
に設定すると、test sanitizerが無効になります。
ただしsanitizeExit
をfalse
にしてエラーを無視すると、後続のテストが実行されなくなるので注意が必要です。
Deno.test({
name: "test",
fn() {
Deno.exit();
},
sanitizeExit: false,
});
Deno.test(function name() {
// このテストは実行されない(上のDeno.exit()で強制終了されるため)
console.log("aaa");
});
まとめ
- deno testコマンドで発生する「Test case is leaking async ops.」や「Test case is leaking 1 resource:」「Test case attempted to exit with exit code: 0」エラーはtest sanitizerが検出したもの
- Promiseのawait忘れの該当箇所を発見するには
--trace-ops
フラグを使う - これらのエラーを無視するにはオプションの
sanitizeOps
/sanitizeResources
/sanitizeExit
をfalse
に設定する