Jest 27 について

4 min read読了の目安(約4300字

https://jestjs.io/blog/2021/05/25/jest-27 についてまとめる。筆者は Jest についてあまり詳しくないので、記事中に誤りがあった場合はコメントで指摘していただけると嬉しい。

Jest 27 では Jest 15.0: New Defaults for Jest以降初の大幅なデフォルトの見直しが行われた。

また、そのような変更だけでなく、いくつかの新機能も実装されている。

新機能

比較的小さめなものとしては

  • ESM のネイティブサポートが進んではいるが、モッキングなどの大きな複雑な問題が残っている。
  • シンボリックリンクとして置かれたテストファイルも適切に扱えるようになった。これは Bazel ユーザーが主に望んていたようだ。
  • transformを非同期にできるようになった。これは esbuild や snowpack、vite などのツールを介したトランスパイルを効果的にサポートするために必要だったらしい。

があるようだ。

ユーザーにある程度影響がありそうなものは以下。

スナップショットではないテストのインタラクティブモード

そして、Jest のスナップショットテストにはインタラクティブモードという機能がある。

https://jestjs.io/ja/docs/snapshot-testing#インタラクティブ・スナップショットモード

--watch でスナップショットテストを実行しているときにテストが失敗した場合、インタラクティブモードに入ることができる。インタラクティブモードでは、失敗したテストが複数ある場合でも1つのテストごとにステップ実行してくれる。これによって失敗したテストを1つずつ確認できる。

このインタラクティブモードが、スナップショットではない通常のテストでも使えるようになった。

インラインスナップショットのために Prettier を使う必要がなくなった

Jest にはインラインスナップショットテストという機能がある。

https://jestjs.io/ja/docs/snapshot-testing#インラインスナップショット

通常スナップショットテストでは、テスト本体とは異なるファイル(.snap)に結果を書き込む。一方インラインスナップショットテストでは、結果をテスト本体のファイルに書き込むことができる。

次のような React コンポーネントのテストを考える。

it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="https://prettier.io">Prettier</Link>)
    .toJSON();
  expect(tree).toMatchInlineSnapshot();
});

このとき、次回テストを実行したタイミングで、treeが評価されこのテストファイルにスナップショットが書き込まれ、toMatchInlineSnapshotの引数になる。つまり、たとえば次のようにファイルが変更される。

it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="https://prettier.io">Prettier</Link>)
    .toJSON();
  expect(tree).toMatchInlineSnapshot(`
<a
  className="normal"
  href="https://prettier.io"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  Prettier
</a>
`);
});

Jest 26 まではこの機能を使うために Prettier を使う必要があった。

Jest 27 からはこの機能を使うために Prettier をわざわざ依存に入れる必要がなくなった。

なんとなく変更差分を読んだ感じだと Prettier が存在しない場合は Babel を使ってコードのパースと生成を行っているようだ。

筆者はこんな機能が Jest にあることを知らなかったが、Jest のメンテナーである SimenB がよく Prettier にバグ報告をくれるのはこのあたりで Prettier をよく使っていたのもあったのだろうなーと納得している。かなりクレイジーな機能だと思う。

デフォルトの変更

デフォルトのテストランナーが jest-circus に

今までデフォルトの設定で Jest を利用していた場合、何年も前に Jasmine2.0 からフォークされたコードを実行してしまっていた(describe とか it とか beforeEach とかそういうやつ)。

2017年に、エラーメッセージや保守性や拡張性などの改善のため、それらの Jasmine ベースのコードの代わりとなる jest-circus というソフトウェアが開発された。

jest-circus を使ったテストが Facebook で長年に渡って大規模に運営されており、もちろん Jest 内でも使われており、最近では create-react-app でも使用されるようになったようだ。それらの運用を経て jest チームは jest-circus が jest-jasmin2 と高い互換性を持つに至ったと確信し、デフォルトのテストランナーを jest-circus に変更することを決定した。

実行順序などの厳密なところでは微妙に違いがあるかもしれないが、基本的には困難なくアップグレードが可能なようだ。

もし Jasmine ベースのテストランナーに戻したい場合は "testRunner": "jest-jasmine2" を指定することで戻すこともできる。

また、これに伴ってデフォルトの testEnvironment"jsdom" から "node" に変更された。

なので、たとえば今まで document を使用したテストなど jsdom 環境に依存したテストを書いていた場合、デフォルト設定のままだとテストが失敗するようになる。そのような場合は "testEnvironment": "jsdom" を指定する必要がある。

DOM を使ったテストと Node 環境のテストが混在している場合は、基本的には Node 環境として扱い(つまりデフォルトのtestEnvironmentを使う)、必要に応じてコメントで testEnvironment を指定することが推奨される。

/**
 * @jest-environment jsdom
 */

test('use jsdom in this test file', () => {
  const element = document.createElement('div');
  expect(element).not.toBeNull();
});

また、次のメジャーでは Jest のコアから jest-jasmine2 と jest-environment-jsdom が削除される予定になっている。そのため、Jest 28 からは、jasmine ベースのテストランナーを使いたい場合や、jsdom 環境でのテストを実行したい場合には明示的にそれらのパッケージをインストールする必要が出てくる。

デフォルトで新しい疑似タイマーが使われるように

Jest 26 から、Timer Mocksと呼ばれるモダンな疑似タイマー機能が実装されていた。

Jest 27 からはこの新しい擬似タイマーがデフォルトになるようだ。

もし以前の古い疑似タイマーを使う必要がある場合はjest.useFakeTimers("legacy")を実行したり、Jestの設定に"timers": "legacy"を指定したりすれば以前のものを使える。

おわりに

まだ微妙に紹介しきれていないところがあるので、気になる人は

を読んでほしい。

jsdom を使ったテストを書いている人からしたら少々大きい変更かもしれないが、Node.js 環境でしかテストを書いていないユーザーは特になんの苦労もなくアップデートができると思う。しかし、内部的にはかなり改善されているようだ。