エディタからJestのテストを実行する際に複数の設定を自動で切り替える方法(VS CodeとJetBrains IDE)
この記事はCommune Advent Calendar 2024シリーズ1の13日目の記事です。
やりたいこと
サーバーサイドの開発をしている現場ではユニットテストとは別にDBのセットアップ処理を伴う統合テストを実行できるようにしている場合があります。DBのセットアップ処理やリセット処理などは時間がかるため、ユニットテストには含めないようにして別の設定ファイルに含める方法を採る場合が多いと思います。
コマンドラインから実行する場合にはテストフレームワークの実行用設定ファイルを引数に渡すことで変更できます。
# ユニットテスト(デフォルトの設定ファイルが利用される)
npm exec jest --
# 統合テスト(明示的に別の設定ファイルを指定)
npm exec jest -- --config jest.api.config.js
テストはできればエディタ上から実行したいです。これはエディタから実行した方が下記のようなメリットがあるためです。
- 結果が見やすくなる
- エディタとターミナル間でのフォーカスの切り替えが不要になる
- デバッグの実行が簡単になる
特に何も設定を変更しない場合、エディタからテストを実行する場合にはデフォルトの設定しか利用されないためユニットテストしか実行できない、あるいは常にDBのセットアップ処理が走ってしまうという問題が発生します。エディタから実行する際にテストの種類に応じて都合良く設定を切り替えて実行したいのですが、今回Jestでこれを実現する方法を調査したのでVS CodeとJetBrains IDEの場合の設定方法を共有したいと思います。
VS Codeの場合
vscode-jestの拡張を使っている場合はVirtual Folders機能を使うと簡単に設定することが可能です。下記は単体テストとAPIテスト用の設定を別々にした場合のVirtual Foldersの設定例です。
module.exports = {
testEnvironment: 'node',
testMatch: ['**/*.(test|spec).ts'],
transform: {
'^.+.tsx?$': ['ts-jest', {}],
},
}
const base = require('./jest.config.js')
module.exports = {
...base,
testMatch: ['**/*.api-test.ts'],
}
{
"jest.virtualFolders": [
{
"name": "Unit Tests",
},
{
"name": "API Tests",
"jestCommandLine": "npm exec jest -- --config=jest.api-test.config.js"
}
]
}
次の画像は上記の設定を行った場合のapp.api-test.ts
の表示です。 app.api-test.ts
はjestのデフォルトのテストの拡張子ではないですが、jest.api-test.config.js
内でテスト対象として定義しているのでテストとして認識されて、テスト実行用ボタンが表示されています。
JetBrains IDEの場合
JetBrainsのIDE(WebStormなど)で利用できるJestプラグインではvscode-jestのように複数の設定からテスト対象を認識して実行時に設定を切り替えるような機能はありません。このためvscode-jestのような挙動をJetBrainsのIDEで実現するには引数に応じてjest.config.js
内で実行時に動的に設定を切り替えるしかないです。
すべてのパターンに対応して切り替えを行うのは大変なので、今回は特定の1つのテストファイルでテストを実行する場合にのみ、設定ファイルを動的に変更する例を提示します(特定のディレクトリ以下のテストを全実行するような場合にはもう一工夫必要です)。
次の設定はjest.config.js
では関数をexportすることで動的に設定を変更できるようにし、jest.api-test.config.js
ではこの関数を実行した結果をexportすることで常に同じ設定を利用できるようにしています。これによってCLIによる実行やVS Code側で行った設定と共存させています。
const baseConfig = {
testEnvironment: 'node',
testMatch: ['**/*.(test|spec).ts'],
transform: {
'^.+.tsx?$': ['ts-jest', {}],
},
}
const apiConfig = {
...baseConfig,
testMatch: ['**/*.api-test.ts'],
}
const TestTypes = {
UNIT: 'unit',
APITEST: 'api-test',
}
const Configs = {
[TestTypes.UNIT]: baseConfig,
[TestTypes.APITEST]: apiConfig,
}
function getTestType() {
const configs = process.argv
.map(arg => {
const matched = arg.match(new RegExp(`\\.(test|spec|${TestTypes.APITEST}).ts$`))
if (!matched) return undefined
return ['test', 'spec'].includes(matched[1]) ? TestTypes.UNIT : matched[1]
})
.filter(Boolean)
if (configs.length < 1 || configs.length > 1) {
return TestTypes.UNIT
}
return configs[0]
}
const getConfig = type => {
const _type = type ?? getTestType()
const config = Configs[_type]
if (!config) {
throw new Error(`Unknown test type: ${_type}`)
}
return Configs[_type]
}
module.exports = getConfig
const getConfig = require('./jest.config.js')
module.exports = getConfig('api-test')
Discussion