サーバーサイドのESMプロジェクト(TypeScript)のテストをts-jestとesbuild-jestで実行する
の続き。
jestで実行する際も色々ハマりどころがあったのでメモ。
TypeScriptプロジェクトなので、ts-jestとesbuild-jestのそれぞれを使ってやってみた。
検証環境
- Linux: Ubuntu22.04LTS @WSL2(Windows11)
- nodejs 18.13.0LTS
- pnpm@7.26.3
- npmやyarnを使っている人は適宜読み替えてください
- (TypeScript: 4.9.5)
セットアップ
package.jsonで"type": "module"
を設定し、tsconfig.jsonは前回記事のような感じ
で書いておく
テストコード
ESM特有の機能を使えていることを確認したいので、意味は無いがtop-level-awaitを含むテストコードがエラー無しで実行することを確認する。
/** 10msec待機した後に文字列をtop-level-awaitで取得 */
const awaitedValue: string = await (async () => {
await new Promise((resolve) => setTimeout(resolve, 10));
return 'awaited';
})();
test('top level await', () => {
expect(awaitedValue).toBe('awaited');
});
ts-jestで実行
下記のパッケージをインストールする:
pnpm add -D jest @types/jest ts-jest ts-jest-resolver
ts-jest-resolverは、自作モジュールなどのimportで拡張子.jsなどをつけているのを正しく読み込むために必要。
次にjest.config.cjs
を記述する。(※ESMプロジェクトなので拡張子はcjsにする必要)
/** @type {import('jest').Config} */
const config = {
resolver: 'ts-jest-resolver',
extensionsToTreatAsEsm: ['.ts'],
transform: {
'^.+\\.(t|j)sx?$': [
'ts-jest',
{ useESM: true }
],
},
};
module.exports = config;
- resolverで先程の
ts-jest-resolver
を指定し、jestが正しくモジュールのimportが出来るようにする - extensionsToTreatAsEsmで拡張子が
.ts
のファイルをESMとして扱うように指定している - transformで
ts-jest
を指定しており、更にオプションでuseESM: true
を指定している
ts-jestのESM Supportのページ:
も参照のこと。最低限の設定は以上だが、実行の際にはまだ注意点があり、NODE_OPTIONS=--experimental-vm-modules
を指定して実行する必要がある。
すなわち、
"scripts": {
// ...
+ "test": "NODE_OPTIONS='--experimental-vm-modules' jest"
},
などのようにして、jest実行時に当該環境変数をセットして実行する必要がある。
セットしない場合、SyntaxError: Cannot use import statement outside a module
とかSyntaxError: Cannot use 'import.meta' outside a module
、ReferenceError: await is not defined
など色々とエラーが発生することがある。
とりあえず以上のような感じでなんとか動くようになる。
pnpm test path/to/tla.test.ts
> test-esm-app@1.0.0 test /home/xxxxxx/test-esm-app
> NODE_OPTIONS='--experimental-vm-modules' jest "test/unit/tla.test.ts"
(node:29140) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS path/to/tla.test.ts
✓ top level await (7 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.584 s, estimated 2 s
その他注意
に記載の通りだが、ESMプロジェクトをjestで実行する際にはいくつかcommonjsのときと違いがあり、例えばjestオブジェクトをimport無しで使うことが出来なかったりする。
// 公式ドキュメント例より
import { jest } from "@jest/globals"; // 明示的にimportする
jest.useFakeTimers();
// etc.
// alternatively
import.meta.jest.useFakeTimers();
// jest === import.meta.jest => true
なお、明示的にimportする場合だが、パッケージマネージャーにpnpmを使っていてかつnode-linker設定がデフォルトになっている場合はnode_modulesの構造上の問題でそのままimport {jest} from '@jest/globals'
をすることが出来ない。
この場合は
- .npmrcに
node-linker=hoisted
を設定する -
pnpm add -D @jest/globals
をして明示的に@jest/globals
をインストールする - 上記例の
alternatively
となっている、import.meta.jest.***
を使って代替する。
などの対策を取る必要が出てくる。
esbuild-jestを使う場合
ts-jestは実行が遅いので、高速化などの目的でesbuild-jestも使ってみる。
(最終更新が2021年3月で停滞気味なところは微妙だが。。。)
インストールするパッケージは以下のような感じ:
pnpm add -D jest @types/jest esbuild-jest ts-jest-resolver
ts-jestをesbuildに変えただけとなっている。
jest.config.cjsの内容は以下のような感じ:
/** @type {import('jest').Config} */
const config = {
resolver: 'ts-jest-resolver',
extensionsToTreatAsEsm: ['.ts'],
transform: {
'^.+\\.(t|j)sx?$': [
'esbuild-jest',
{
// sourceMaps: true, // ←必要な場合
format: 'esm',
target: 'es2022',
},
],
},
};
module.exports = config;
ts-jestの場合と差分があるのはtransform
部分だけ。
- ts-jestでなくesbuild-jestをtransformに使うようにしている
- format: esmを指定する
- target: es2022以降を指定する
あたりが最低限必要になる。
それ以降はts-jestの場合と同じで、
jest実行時にNODE_OPTIONS=--experimental-vm-modules
を指定する必要があったり、jestオブジェクトの利用に関しての注意点も共通となる。
これで同じテストを実行すると、
pnpm test path/to/tla.test.ts
> test-esm-app@1.0.0 test /home/xxxxxx/test-esm-app
> NODE_OPTIONS='--experimental-vm-modules' jest "test/unit/tla.test.ts"
(node:29722) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS path/to/tla.test.ts
✓ top level await (2 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.227 s, estimated 1 s
無事に動作し、実行時間が約1.5秒 → 約0.2秒と高速化されていることがわかる。
(ただし、その分型チェックはされなくなっているはずなので注意)
雑感
エコシステムの成熟度合いや先行事例の多さなどを考えてjestでセットアップする方法を調べたが、vitestもかなり良さそう。
jestだとESM対応はまだExperimental扱いだがvitestだとネイティブでESMサポートされているらしく?、手元で少しだけ試してみた感じでは特別な設定は不要そうだった。
こういったツールがより成長してくれると色々楽になっていくかもしれない。
Discussion