Sinon.JSでuseFakeTimersを使って現在時刻を変更して単体テストをしていたとき、setTimeoutがおかしくなったので調べてみました。
公式サイトのドキュメント
sinonのAPIリファレンスに説明がちゃんと書いてありました。
(なのでもうここまででこの記事は99%の内容になります。あとは蛇足です。)
APIリファレンスには以下のように書かれていました。
Causes Sinon to replace the global setTimeout, clearTimeout, setInterval, clearInterval, setImmediate, clearImmediate, process.hrtime, performance.now(when available) and Date with a custom implementation which is bound to the returned clock object.
StackOverflowでも同じ内容の質問があり、sinonのuseFakeTimersを使っている間はsetTimeoutは使えないよ、と書かれていました。
つまり、sinonでuseFakeTimersを使っている間、setTimeoutやsetInterval等それとDateオブジェクトのカスタム実装のようなJavaScriptで時間の操作が入るような関数は正常に動作しなくなります。
検証
useFakeTimersを使ったときにsetTimeoutがどのような動きになるのかを検証しました。
使ったJavaScriptファイルは以下のリポジトリに格納しているので、自由に利用してください。
nodejs-module-labo/sinon/useFakeTimers-break at main · s1r-J/nodejs-module-labo
まず、下のようにsinonのuseFakeTimersを呼び出し、時刻を好きな時刻にします
単体テストであれば、現在時刻ではなくテストに最適な時刻を指定して利用します。
const sinon = require('sinon');
const now = new Date();
const timeStub = sinon.useFakeTimers(now.getTime()); // ①
console.log(`now: ${now}`); // ②
setTimeout(() => { // ③
console.log(`timeout: ${new Date()}`); // ④
}, 2000);
console.log('end'); // ⑤
このコードではsetTimeoutを使っている(③)ため、sinon.useFakeTimersによる影響がなければ以下のようなコンソール出力が得られるはずです。
JavaScriptは非同期のため、②の実行後に⑤が実行され、そのおよそ2秒後にsetTimeoutによって④が実行されるはずです。
now: Fri Apr 01 2022 12:00:00 GMT+0900 (Japan Standard Time)
end
timeout: Fri Apr 01 2022 12:00:02 GMT+0900 (Japan Standard Time)
しかし、実際のコンソール出力は以下のように、④が実行されなくなります。
setTimeoutのような時間操作が関わるメソッドはsinon.useFakeTimersを利用している間は利用できなくなります。
now: Fri Apr 01 2022 12:00:00 GMT+0900 (Japan Standard Time)
end
ただし、先述のJavaScriptコードの②と③の間でtimeStub.restore();
を実行することでsetTimeoutを正常に動作させることができます。
restoreはsinon.useFakeTimersの利用を終了して、スタブを片付けるメソッドです。
結論
- sinonのuseFakeTimersを使っている間は、JavaScriptで時間の操作が入るような関数(setTimeoutやsetInterval等、Dateオブジェクトのカスタム実装)は正常に動作しない
- sinonのuseFakeTimersを使ったあとはきちんとrestoreする
- restoreしないと他のテストケースでsetTimeoutなどが正しく動作しなくなる