Open37

Jestを読む

nus3nus3

動作確認

jestパッケージ自体はmonorepo配下で管理されている
https://github.com/jestjs/jest/blob/8c78a08c6e75c1a0c4447aa8a0c61ad9e395e16f/package.json#L54

なのでjestの実装を変更して(例えばconsole.logの追加など)、動作確認をする場合は以下の作業をする

# リポジトリのルートで変更分をビルド
yarn watch

# 適当なテストケースを実行
# yarn jest -- {任意に作成したテストファイルのパス}
yarn jest -- packages/jest-util/src/__tests__/nus3.test.ts
nus3nus3

もしくはmonorepoでjest自体は直接リポジトリ内のパッケージを見ているのでexamples配下のテストを実行することでも動作確認ができる。

examples/getting-started配下でyarn testを実行するなど

nus3nus3

packages

jestを実行すると、monorepoで管理されたpackages配下のパッケージが実行されるので、依存するパッケージを出てくる順番でみてみる

パッケージの実行順

実際にテストを実行している部分を、パッケージごとにみてみる

jestを実行した時に、呼ばれるパッケージ

  1. jest
  2. jest-cli
  3. @jest/core
  4. jest-runner
  5. jest-circus
nus3nus3

2024/04/09まで読んでみてのイメージ

  1. テストファイルと、その対象のファイル、そのほか依存しているモジュールに対してbabelでトランスパイルし、requireしておく
  2. テストファイルの中で定義されたテストコードを実行する
  3. テストコードはtry catchの中で実行し、実行したテストコードがerrorをthrowしたら、その呼び出しもとでテストは落ちたと判定するように
  4. テスト結果を格納する
nus3nus3
nus3nus3

runJest()に渡されたonCompleteprocessResults()で実行される
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/runJest.ts#L323-L330

以下はprocessResults()onCompleteが実行されている箇所
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/runJest.ts#L126

動作確認してみると、ここではprocessResults()の第一引数として渡されたrunResultsをonCompleteの第一引数に渡している

ということでテスト結果はprocessResults()の第一引数であるresults
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/runJest.ts#L323

このresultsawait scheduler.scheduleTests(allTests, testWatcher);の返り値である
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/runJest.ts#L309

nus3nus3

scheduler.scheduleTestsschedulercreateTestSchedulerで生成される
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/runJest.ts#L301-L304

このschedulerTestSchedulerクラスのインスタンス
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/TestScheduler.ts#L63

TestSchedulerクラスの中にscheduleTestsメソッドが定義されている
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/TestScheduler.ts#L92

scheduleTestsaggregatedResultsという変数を return してる
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/TestScheduler.ts#L319

return される`aggregateResults`の中身
{
  numFailedTestSuites: 0,
  numFailedTests: 0,
  numPassedTestSuites: 1,
  numPassedTests: 1,
  numPendingTestSuites: 0,
  numPendingTests: 0,
  numRuntimeErrorTestSuites: 0,
  numTodoTests: 0,
  numTotalTestSuites: 1,
  numTotalTests: 1,
  openHandles: [],
  snapshot: {
    added: 0,
    didUpdate: false,
    failure: false,
    filesAdded: 0,
    filesRemoved: 0,
    filesRemovedList: [],
    filesUnmatched: 0,
    filesUpdated: 0,
    matched: 0,
    total: 0,
    unchecked: 0,
    uncheckedKeysByFile: [],
    unmatched: 0,
    updated: 0
  },
  startTime: 1711858222218,
  success: true,
  testResults: [
    {
      leaks: false,
      numFailingTests: 0,
      numPassingTests: 1,
      numPendingTests: 0,
      numTodoTests: 0,
      openHandles: [],
      perfStats: [Object],
      skipped: false,
      snapshot: [Object],
      testFilePath: '${HOME}/jest/packages/jest-util/src/__tests__/nus3.test.ts',
      testResults: [Array],
      console: undefined,
      displayName: undefined,
      failureMessage: null,
      testExecError: undefined,
      coverage: undefined
    }
  ],
  wasInterrupted: false
}

をみるとtestResultsプロパティの中にテスト結果が格納されている。
このtestResultsの中にあるtestResultsプロパティを見ると次のようになっている。

`testResults`の中にある`testResults`プロパティの中身
[
  {
    "ancestorTitles": ["nus1"],
    "duration": 3,
    "failing": false,
    "failureDetails": [],
    "failureMessages": [],
    "fullName": "nus1 nus1 testcase1",
    "invocations": 1,
    "location": null,
    "numPassingAsserts": 1,
    "retryReasons": [],
    "startAt": 1711858647818,
    "status": "passed",
    "title": "nus1 testcase1"
  }
]

最初のtestResultsがおそらくファイルごとの結果を配列で格納しており、その中のtestResultsがファイルの中にあるテストケースごとの結果を配列で格納している

nus3nus3

aggregateResultscreateAggregatedResultsの返り値
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/TestScheduler.ts#L108

createAggregatedResultsではテスト結果の初期値を生成してそう
からのテスト結果を生成するのはmakeEmptyAggregatedTestResultが作ってそう
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/TestScheduler.ts#L428-L434

makeEmptyAggregatedTestResultをみるとAggregatedResult型のオブジェクトを生成し、初期値を入れてる
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-test-result/src/helpers.ts#L10-L43
このmakeEmptyAggregatedTestResult@jest/test-resultパッケージの持ち物

TestScheduler.scheduleTestsの中で定義されているonResult
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/TestScheduler.ts#L115-L154
この関数の中でaddResult()を呼ぶことでAggregatedResultにテスト結果を記してそう
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/TestScheduler.ts#L147

実際に@jest/test-resultが提供するaddResult()では、テスト結果を引数に受け取り AggregatedResult にテスト結果を追加している
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-test-result/src/helpers.ts#L86-L153

addResultの引数に渡すテスト結果は onResult の引数に渡されるものでもある。実際にonResultTestScheduler.scheduleTestsの中で呼ばれているのは以下の 2 箇所
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/TestScheduler.ts#L255-L257
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/TestScheduler.ts#L281-L288

単純なテストファイルを手元で実行してみるとtestRunner.supportsEventEmittersが true の場合の以下の処理が実行されていた
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/TestScheduler.ts#L255-L257

nus3nus3

@jest/test-result

https://www.npmjs.com/package/@jest/test-result

makeEmptyAggregatedTestResult()

テスト結果の初期値入りオブジェクトを生成する関数。
TestScheduler.scheduleTestsの中で使用される
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-test-result/src/helpers.ts#L10-L43

nus3nus3

jest-runner

TestRunner.on

TestScheduler.scheduleTestsの中でテストが実行されていそうな以下の部分で使われている
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-core/src/TestScheduler.ts#L255-L257

定義されている場所は以下
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-runner/src/index.ts#L200-L205

nus3nus3

TestRunner.onではTestRunnerのプライベートメソッドであるthis.#eventEmitter.onを実行してる
this.#eventEmitteremitteryというパッケージを require している
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-runner/src/index.ts#L51
https://github.com/sindresorhus/emittery
このemitteryは非同期でイベントの listner や emit がシンプルにできるライブラリっぽい
TestRunner.onでは第一引数で指定したイベント名の listner を登録しており、this.#eventEmitter.emitで登録したイベントを実行してると推測

nus3nus3

this.#eventEmitter.emit('test-file-success', [test, result])で emit の際に渡される result はrunTestの返り値である
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-runner/src/index.ts#L77-L84

jest-runnter パッケージの中にrunTestは定義されていて、runTestInternalの返り値が result に格納されている
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-runner/src/runTest.ts#L385-L413

runTestInternalも同一ファイルに定義されている
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-runner/src/runTest.ts#L78-L383

nus3nus3

runTestInternalの返り値である resulttestFrameworkの返り値である
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-runner/src/runTest.ts#L306-L313

このtestFrameworktransformer.requireAndTranspileModuleの返り値
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-runner/src/runTest.ts#L114-L119

transformer.requireAndTranspileModuleの定義は@jest/transformで定義されている
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-transform/src/ScriptTransformer.ts#L772-L838

examples/getting-started のテストを実行した際のrequireAndTranspileModuleの第一引数は以下だった

${HOME}/jest/packages/jest-runner/build/index.js
${HOME}/jest/packages/jest-environment-node/build/index.js
${HOME}/jest/packages/jest-circus/build/runner.js

requireAndTranspileModuleの処理の中で出てくるaddHookはモジュールを require 時に特定のコードを変換する処理を挟めるライブラリっぽい
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-transform/src/ScriptTransformer.ts#L788-L813
https://github.com/danez/pirates

nus3nus3

requireAndTranspileModuleの第一引数に渡した path のファイルを require してる認識で良さそ
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-transform/src/ScriptTransformer.ts#L815-L818

testFrameworktransformer.requireAndTranspileModuleの第一引数に渡すモジュールを require してそう

testFrameworkの値はrequireAndTranspileModuleの第一引数の値が${HOME}/jest/packages/jest-circus/build/runner.jsの時

ということで result の値はjest/packages/jest-circus/build/runner.jsのモジュールが実行した返り値

nus3nus3

jest-circus

実際にテストを実行して結果を出力してそうなパッケージ

https://www.npmjs.com/package/jest-circus

nus3nus3

テスト結果を返すtestFramework()の実態はpackages/jest-circus/src/runner.tsにある。
このモジュールはpackages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.tsが export するモジュールを使っている

対象のテストファイルのパスを引数に渡して、esm か cjs かをruntime.unstable_shouldLoadAsEsm(path)で判断してる
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.ts#L91

unstable_shouldLoadAsEsm()jest-runtimeで定義されており、wasmファイルでなければ実態はResolver.unstable_shouldLoadAsEsm()である
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-runtime/src/index.ts#L412-L420

Resolver.unstable_shouldLoadAsEsm()./shouldLoadAsEsmでexportされており
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-resolve/src/resolver.ts#L185
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-resolve/src/resolver.ts#L22

実態はcachedShouldLoadAsEsm
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-resolve/src/shouldLoadAsEsm.ts#L25-L41

cachedShouldLoadAsEsmの中でshouldLoadAsEsmが実行されており、runtime.unstable_shouldLoadAsEsm(path)ではファイルの拡張子か package.json の type を見て esm か cjs かを判定している

esm か cjs か判定して、テストファイルを require か import する
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.ts#L93-L97

nus3nus3

対象の CJS ファイルを babel でトランスパイルしつつ、読み込み、その後runAndTransformResultsToJestFormat()を実行し、結果を格納してる
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.ts#L104-L109

runAndTransformResultsToJestFormat()の実装は以下
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts#L146-L245
で、run()を実行している

run()の実装は以下
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/run.ts#L35-L45

test は以下の分岐で実行されてそう。関数で言うと_runTest
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/run.ts#L114-L136

_runTestの実装は以下
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/run.ts#L199-L246

_runTestの中で_callCircusTestが実行されている
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/run.ts#L236

_callCircusTestの実装は以下
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/run.ts#L273-L308

_callCircusTestではcallAsyncCircusFnが呼ばれており、その実装箇所は以下
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/utils.ts#L195-L318

このcallAsyncCircusFnではtest変数にあるfnの中身は以下のようになっている

function () {
  expect(sum(1, 2)).toBe(3);
}

https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/utils.ts#L277

try catch の中で、このfnを実行することでテストが落ちているかどうかを判定している。またこの実行を Promise で行い、何かしら error を throw するようであれば reject する

呼び出し元では reject されるようであればそれを catch し、テストが落ちたと判定してる
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/run.ts#L285-L307

nus3nus3

TODO: なぜfn.call(testContext)が実行できるのか
これはテストコードのみでsum()はどこにも定義されていないはず
引数で渡されているtestContextの空オブジェクトのようだし

fn.callで呼ばれていることでfnが定義されている大元のtestからだと、テスト対象のモジュールにもアクセスできるのか?

nus3nus3

jest-runtime

jest-circusが定義する jestAdapter に渡される runtime 部分の実装がある

nus3nus3

requireModule

今回、動作確認をしているのはexamples/getting-started/sum.test.jsで CJS だし、ESM はそもそも jest だと ESM 対応が experimental なので CJS のやつを見る

requireModuleは以下に定義されている
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-runtime/src/index.ts#L919-L1016

requireModuleには引数が 4 つあるが、使われているのは from のみで、from は該当のテストのパス

以下で、manual モックの場合に、require の対象のパスを manual モックのパスに変更してたりする?
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-runtime/src/index.ts#L936-L948

modulePaththis._resolveCjsModule(from, moduleName)の返り値
this._resolveCjsModule(from, moduleName)では、moduleName が指定されない限りは、fromの値を返す
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-runtime/src/index.ts#L1448-L1454

moduleRegistryの値は、今回動作確認してるテストだとここを通る
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-runtime/src/index.ts#L978-L980

this._moduleRegistryの値は Map になっている
mainが自身の値を受け取っている(循環参照)

Map(0) {}
Map(1) {
  '/$HOME/jest/examples/getting-started/sum.test.js' => <ref *1> {
    children: [],
    exports: {},
    filename: '/$HOME/jest/examples/getting-started/sum.test.js',
    id: '/$HOME/jest/examples/getting-started/sum.test.js',
    isPreloading: false,
    loaded: false,
    path: '/$HOME/jest/examples/getting-started',
    parent: [Getter],
    paths: [
      '/$HOME/jest/examples/getting-started/node_modules',
      '/$HOME/jest/examples/node_modules',
      '/$HOME/jest/node_modules',
      '/$HOME/node_modules',
      '/$USER/dev/node_modules',
      '/$USER/node_modules',
      '/Users/node_modules',
      '/node_modules'
    ],
    main: [Circular *1]
  }
}
nus3nus3

this._loadModuleでモジュールを読み込んでるのかな
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-runtime/src/index.ts#L1002-L1008

this._loadModuleの実装
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-runtime/src/index.ts#L1129-L1162

テストファイルの拡張子は.js なので以下の分岐に進み、this._execModuleが実行される
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-runtime/src/index.ts#L1152-L1159

fromをテストパスだけにすると、console.log が 2 回実行されてる。なんでだ
テストファイルと、テストファイルが import(require)してるファイルが map に格納されてるからっぽい

this._execModuleの実装
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-runtime/src/index.ts#L1542-L1663

this._execModuleに渡される localModule の filename が'/Users/nus3/dev/fork/jest/examples/getting-started/sum.test.js''/Users/nus3/dev/fork/jest/examples/getting-started/sum.js'の場合が対象のテストファイルを load してる場合だ

this.transformFileをした時点で、テストファイルとテストファイルが require したテスト対象のファイルはトランスパイルされている
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-runtime/src/index.ts#L1592

// sum.test.js
"use strict";

var sum = require("./sum");
test("adds 1 + 2 to equal 3", function () {
  expect(sum(1, 2)).toBe(3);
});

// sum.js
function sum(a, b) {
  return a + b;
}
module.exports = sum;

この transform 処理はjest-transformパッケージの以下で定義されている
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-transform/src/ScriptTransformer.ts#L722-L749

呼び出し元を辿っていくと_instrumentFile()という関数の中でbabelTransformが呼ばれてる
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-transform/src/ScriptTransformer.ts#L350-L377

このbabelTransformbabel-coretransformSyncを使っている
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-transform/src/ScriptTransformer.ts#L10

どうやら対象のテストファイルは babel を使ってトランスパイルされてる

nus3nus3

jest-resolve

jest-runtimeで使っていたunstable_shouldLoadAsEsmの大元の実装はこのパッケージにある

cachedShouldLoadAsEsm()

jest-circusで使っていたruntime.unstable_shouldLoadAsEsm(path)の大元の実装部分。esm の判定を拡張子でやってる

詳細は以下のリンクに記載
https://zenn.dev/link/comments/9360bb8eea8547

nus3nus3

どのようにしてテスト関数を実行しているのか

例えば examples にある sum のテストの場合
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/examples/getting-started/sum.test.js#L5-L7

最終的に jest の内部では、以下の部分でテスト関数を実行している
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/utils.ts#L203
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/utils.ts#L277

このfnは以下のようになっている

function () {
  expect(sum(1, 2)).toBe(3);
}

このfnを実行するだけでは、expectsumが require されていないため、テストが実行できないのでは?

テスト実行時にはjest-runtimerequireModulejest-circusjestAdapter(projectConfig.testRunnerに指定されている path)で呼んでいる
https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.ts#L93-L97

テストファイルやテストファイルで呼び出されているモジュールはruntime.requireModuleを実行する際に babel でトランスパイルされている
https://zenn.dev/link/comments/25bd5d28c8fef7

runtime.requireModuleでは、テストファイルで呼んでいるモジュールも localModule の exports に追加している
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-runtime/src/index.ts#L1001-L1015

の部分の出力はそれぞれ以下になり、テストファイルが依存しているexamples/getting-started/sum.jsの関数であるsumがトランスパイルされた状態でlocalModule.exportsに追加されている

if (from === "/jest/examples/getting-started/sum.test.js") {
  console.log(modulePath);
  console.log(localModule.exports);

  // それぞれ出力は以下

  // /examples/getting-started/sum.js
  // [Function: sum]
  // /examples/getting-started/sum.test.js
  // {}
}

テストファイルで定義した関数を実行する前にjest/packages/jest-circus/build/jestAdapterInit.jsinitializeを実行している
これがあることで、expectsumなどの依存しているモジュールを localModule.exports に追加してる?
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.ts#L30-L40

@jest/expectexpectを runtime に突っ込んでそうな雰囲気はある
https://github.com/jestjs/jest/blob/726ca20752e38c18e20aa21740cec7aba7891946/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts#L92-L95

runtime.requireModuleの引数である from が/Users/nus3/dev/fork/jest/examples/getting-started/sum.test.jsの場合だとruntime.requireModuleは 2 回呼ばれており、それぞれ別の引数であるmoduleNameは undefined と./sumが渡されている

nus3nus3
nus3nus3
  • vmを使うことでテストファイルごとにcontextを分けて実行し、他のファイルのテストが干渉しないように
  • モジュールのrequireをテスト環境用に定義し、テストコード単体で実行できるようにする
  • テストコード単体で実行したものをtry catchすることでテストがパスするかどうかを判定
  • 各々のテストファイルは別プロセスで並列して実行する
  • vmでsandbox環境を作り、そこにdescribeやit、expect、mockを注入するイメージ

実装時のメモは以下に
https://github.com/nus3/best-test-framework/blob/main/worker.js

nus3nus3

TODO: この実装を参考に自分てテストランナーを作ってみる