bouzuya.hatenablog.com

ぼうずやのにっき

TypeScript 3.8 -> 3.9 でテストが失敗した

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.stubmod.f を差し替えることで main.tsimport している 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.stubObject.defineProperty(o, { configurable: true, value: /* ... */ }) な property を置き換えることができる。しかし Object.defineProperty(o, { configurable: true, get: () => /* ... */ }) を置き換えることができない。

これらの組み合わせによって sinon.stub(mod, "f") が機能しなくなったことでテストに失敗した。

ひとまず sinon.replaceGetter を使うことで回避した。……だけどモヤモヤしている。

↑に関連して defineProperty を 2 回呼び出したときの挙動などでいろいろハマった。そちらは Twitter に書いた。

https://twitter.com/bouzuya/status/1261217641244442625