TypeScript 3.8 から 3.9 にバージョンアップしたらテストが失敗した。
イメージとしては↓のような形だった。
// mod_f.ts export function f(): void { console.log("Hello"); } // mod.ts export { f } from "./mod_f"; // main.ts import { f } from "./mod"; export function main() { f(); } // main_test.ts import * as assert from "assert"; import * as sinon from "sinon"; import * as mod from "./mod"; import { main } from "./main"; const f = sinon.stub(mod, "f"); main(); assert(f.callCount === 1); // ここで失敗 callCount === 0 になった
mod が全体で共有される object であることを利用している。 sinon.stub で mod.f を差し替えることで main.ts の import している f を置き換えてテストしている。
これがなぜ TypeScript 3.9 で壊れるのかパッと見では分からない。
原因は TypeScript 3.9 の Breaking Changes の Exports Now Use Getters for Live Bindings にある。 https://devblogs.microsoft.com/typescript/announcing-typescript-3-9/#breaking-changes
--module commonjs を選択したときに export ... from ... したものは getter として定義されるようになった。
また sinon.stub は Object.defineProperty(o, { configurable: true, value: /* ... */ }) な property を置き換えることができる。しかし Object.defineProperty(o, { configurable: true, get: () => /* ... */ }) を置き換えることができない。
これらの組み合わせによって sinon.stub(mod, "f") が機能しなくなったことでテストに失敗した。
ひとまず sinon.replaceGetter を使うことで回避した。……だけどモヤモヤしている。
↑に関連して defineProperty を 2 回呼び出したときの挙動などでいろいろハマった。そちらは Twitter に書いた。