Эта статья в основном дает вам глубокое понимание принципов работы Jest, что удобно для ответов на интервью и реальных потребностей бизнеса. Я считаю, что мы уже знакомы с подготовкой Jest, но мы можем быть очень незнакомы с тем, как работает Jest. Давайте войдем в Jest вместе. Внутренне исследуйте вместе. Сначала прикрепите код к нуждающимся студентам, обратите внимание: https://github.com/Wscats/jest-tutorial

Что такое шутка

Jest — это среда тестирования Javascript, разработанная Facebook. Это библиотека JavaScript для создания, запуска и написания тестов.

Jest выпускается в виде пакета NPM и может быть установлен и запущен в любом проекте JavaScript. Jest в настоящее время является одной из самых популярных тестовых библиотек для интерфейса.

Что означает тестирование

С технической точки зрения тестирование означает проверку того, соответствует ли наш код определенным ожиданиям. Например: функция с именем sum (sum) должна возвращать ожидаемый результат с учетом некоторого результата операции.

Есть много типов тестов, и вы скоро будете ошеломлены терминологией, но короткие тесты делятся на три категории:

  • модульный тест
  • Интеграционное тестирование
  • E2E-тест

Откуда мне знать, что тестировать

С точки зрения тестирования, даже самый простой блок кода может запутать новичков. Самый распространенный вопрос: «Откуда мне знать, что тестировать?».

Если вы пишете веб-страницу, хорошей отправной точкой будет тестирование каждой страницы приложения и каждого взаимодействия с пользователем. Однако веб-страница также должна состоять из единиц кода, таких как функции и модули, которые необходимо протестировать.

Чаще всего возникают две ситуации:

  • Вы наследуете устаревший код, в котором нет встроенных тестов.
  • Вы должны внедрить новую функцию из воздуха

так что мне теперь делать? В обоих случаях вы можете думать о тесте как о проверке того, дает ли функция ожидаемый результат. Наиболее типичный процесс тестирования выглядит следующим образом:

  • Импортируйте функцию для тестирования
  • Дайте функции вход
  • Определите желаемый результат
  • Проверьте, выдает ли функция ожидаемый результат

Как правило, это так просто. Освойте следующие основные идеи, писать тесты больше не будет страшно:

Ввод -> Ожидаемый результат -> Результат утверждения.

Тестовые блоки, утверждения и сопоставители

Мы создадим простой код функции Javascript для сложения 2 чисел и напишем для него соответствующий тест на основе Jest.

const sum = (a, b) => a + b;

Теперь для тестирования создайте тестовый файл в той же папке и назовите его test.spec.js. Этот специальный суффикс является соглашением Jest и используется для поиска всех тестовых файлов. Мы также импортируем тестируемую функцию, чтобы выполнить тестируемый код. Шуточные тесты следуют стилю тестов BDD. Каждый тест должен иметь основной тестовый блок test, а тестовых блоков может быть несколько. Теперь вы можете написать тестовые блоки для метода sum. Здесь мы пишем тест, чтобы добавить 2 числа и проверить ожидаемый результат. Мы предоставим числа 1 и 2 и ожидаем, что на выходе будет 3.

test Требуется два параметра: строка для описания тестового блока и функция обратного вызова для переноса фактического теста. expect оборачивает целевую функцию и объединяет ее с сопоставителем toBe, чтобы проверить, соответствует ли результат вычисления функции ожиданиям.

Это полный тест:

test("sum test", () => {
  expect(sum(1, 2)).toBe(3);
});

Наблюдаем приведенный выше код и находим две точки:

Блок test — это отдельный тестовый блок, который имеет функцию описания и разделения области видимости, то есть представляет собой общий контейнер для теста, который мы хотим написать для функции расчета sum. -expect является утверждением. Этот оператор использует входные данные 1 и 2 для вызова метода sum в тестируемой функции и ожидает выход 3. -toBe — это сопоставитель, используемый для проверки ожидаемого значения, если ожидаемый результат не достигнут, должно быть исключение. брошенный.

Как реализовать тестовый блок

Тестовый блок на самом деле не сложный. Простейшая реализация выглядит следующим образом. Нам нужно сохранить функцию обратного вызова фактического теста тестового пакета, поэтому мы инкапсулируем метод dispatch для получения типа команды и функции обратного вызова:

const test = (name, fn) => {
  dispatch({ type: "ADD_TEST", fn, name });
};

Нам нужно создать функцию обратного вызова с именем state глобально, чтобы сохранить тест. Функция обратного вызова теста хранится в массиве.

global["STATE_SYMBOL"] = {
  testBlock: [],
};

В настоящее время методу dispatch нужно только идентифицировать соответствующие команды и сохранить тестовую функцию обратного вызова в глобальном файле state.

const dispatch = (event) => {
  const { fn, type, name } = event;
  switch (type) {
    case "ADD_TEST":
      const { testBlock } = global["STATE_SYMBOL"];
      testBlock.push({ fn, name });
      break;
  }
};

Как реализовать утверждения и сопоставители

Библиотека утверждений также очень проста в реализации. Вам нужно только инкапсулировать функцию, чтобы предоставить метод сопоставления, удовлетворяющий следующей формуле:

expect(A).toBe(B)

Здесь мы реализуем часто используемый метод toBe, когда результат не равен ожидаемому, просто выдает ошибку:

const expect = (actual) => ({
    toBe(expected) {
        if (actual !== expected) {
            throw new Error(`${actual} is not equal to ${expected}`);
        }
    }
};

На самом деле, try/catch используется в тестовом блоке для перехвата ошибок и вывода информации о стеке для обнаружения проблемы.

В простых случаях мы также можем использовать модуль assert, который поставляется с Node, для создания утверждений. Конечно, есть много более сложных методов утверждения, и принципы схожи по сути.

Интерфейс командной строки и конфигурация

После написания теста нам нужно ввести команду в командной строке для запуска одиночного теста. Обычно команда похожа на следующую:

node jest xxx.spec.js

Суть тут в разборе параметров командной строки.

const testPath = process.argv.slice(2)[0];
const code = fs.readFileSync(path.join(process.cwd(), testPath)).toString();

В сложных ситуациях вам также может понадобиться прочитать параметры локального файла конфигурации Jest, чтобы изменить среду выполнения и т. д. Здесь Jest использует сторонние библиотеки yargs, execa и chalk и т. д. для разбора, выполнения и печати команд.

Моделирование

В сложных тестовых сценариях мы не должны избегать шутливого термина: mock (mock)

В документации Jest мы можем найти, что Jest имеет следующее описание симуляции: «Функция симуляции стирает реальную реализацию функции, захватывает вызов функции и параметры, передаваемые в этих вызовах, так что связь между тестовые коды становятся проще»

Короче говоря, симуляцию можно создать, назначив следующие фрагменты кода функциям или зависимостям:

jest.mock("fs", {
  readFile: jest.fn(() => "wscats"),
});

Это простой пример моделирования, который имитирует возвращаемое значение функции readFile модуля fs при тестировании конкретной бизнес-логики.

Как имитировать функцию

Далее мы изучим, как это реализовать. Первый jest.mock. Его первый параметр принимает имя модуля или путь к модулю, а второй параметр — это конкретная реализация метода внешнего воздействия модуля.

const jest = {
  mock(mockPath, mockExports = {}) {
    const path = require.resolve(mockPath, { paths: ["."] });
    require.cache[path] = {
      id: path,
      filename: path,
      loaded: true,
      exports: mockExports,
    };
  },
};

Наше решение на самом деле такое же, как реализация приведенного выше тестового блока test. Вам нужно только найти место для сохранения конкретного метода реализации и заменить его, когда модуль действительно будет использоваться позже, поэтому мы сохраняем его в require In .cache, конечно, мы также можем хранить его в глобальном state.

Реализация jest.fn не сложна. Здесь мы используем замыкание mockFn для хранения замененных функций и параметров, что удобно для последующих тестовых проверок и статистики данных вызовов.

const jest = {
  fn(impl = () => {}) {
    const mockFn = (...args) => {
      mockFn.mock.calls.push(args);
      return impl(...args);
    };
    mockFn.originImpl = impl;
    mockFn.mock = { calls: [] };
    return mockFn;
  },
};

Среда выполнения

Некоторые студенты могли заметить, что в среде тестирования нам не нужно вручную вводить функции test, expect и jest. Каждый тестовый файл можно использовать напрямую, поэтому нам нужно создать запуск, который внедряет эти методы здесь. окрестности.

Виртуальная машина V8 и область действия

Так как все готово, нам остается только внедрить необходимые для тестирования методы в виртуальную машину V8, то есть внедрить область тестирования.

const context = {
  console: console.Console({ stdout: process.stdout, stderr: process.stderr }),
  jest,
  expect,
  require,
  test: (name, fn) => dispatch({ type: "ADD_TEST", fn, name }),
};

После внедрения области действия мы можем запустить код тестового файла на виртуальной машине V8. Код, который я передал здесь, — это код, который был преобразован в строку. Jest выполнит некоторую обработку кода, обработку безопасности и SourceMap здесь. Для шитья и других операций наш пример не должен быть таким сложным.

vm.runInContext(code, context);

До и после выполнения кода разницу во времени можно использовать для расчета времени выполнения одного теста. Jest также предварительно оценит размер и количество отдельных тестовых файлов и решит, следует ли включить Worker для оптимизации скорости выполнения.

const start = new Date();
const end = new Date();
log("\x1b[32m%s\x1b[0m", `Time: ${end - start}ms`);

Запуск одного тестового обратного вызова

После завершения выполнения виртуальной машины V8 глобальный state соберет все упакованные тестовые функции обратного вызова в тестовом блоке. Наконец, нам нужно только пройти через все эти функции обратного вызова и выполнить их.

testBlock.forEach(async (item) => {
  const { fn, name } = item;
  try {
    await fn.apply(this);
    log("\x1b[32m%s\x1b[0m", `√ ${name} passed`);
  } catch {
    log("\x1b[32m%s\x1b[0m", `× ${name} error`);
  }
});

Функция крюка

Мы также можем добавить жизненные циклы в одиночный процесс выполнения теста, такие как функции ловушек, такие как beforeEach, afterEach, afterAll и beforeAll.

Добавление функции ловушки к вышеуказанной инфраструктуре фактически вводит соответствующую функцию обратного вызова в каждый процесс выполнения теста. Например, beforeEach помещается перед тестовой функцией выполнения обхода testBlock, а afterEach помещается перед testBlock. После обхода выполнения тестовой функции это очень просто. Вам нужно только поставить правильную позицию, чтобы выставить функцию хука любого периода.

testBlock.forEach(async (item) => {
  const { fn, name } = item;
  beforeEachBlock.forEach(async (beforeEach) => await beforeEach());
  await fn.apply(this);
  afterEachBlock.forEach(async (afterEach) => await afterEach());
});

А beforeAll и afterAll можно ставить до и после завершения всех тестов testBlock.

beforeAllBlock.forEach(async (beforeAll) => await beforeAll());
testBlock.forEach(async (item) => {}) +
afterAllBlock.forEach(async (afterAll) => await afterAll());

На данный момент мы реализовали простую тестовую среду. Основываясь на этом, мы можем обогатить метод утверждения, сопоставление и конфигурацию параметров поддержки, а также прочитать личные примечания к исходному коду ниже.

шутка-кли

Загрузите исходный код Jest и запустите его в корневом каталоге.

yarn
npm run build

По сути, он запускает два файла build.js и buildTs.js в папке скрипта:

"scripts": {
    "build": "yarn build:js && yarn build:ts",
    "build:js": "node ./scripts/build.js",
    "build:ts": "node ./scripts/buildTs.js",
}

build.js по существу использует библиотеку babel, создает новую папку сборки в пакете package/xxx, а затем использует transformFileSync для создания файла в папке сборки:

const transformed = babel.transformFileSync(file, options).code;

И buildTs.js по существу использует команду tsc для компиляции файла ts в папку сборки и использует библиотеку execa для выполнения команды:

const args = ["tsc", "-b", ...packagesWithTs, ...process.argv.slice(2)];
await execa("yarn", args, { stdio: "inherit" });

Успешное выполнение будет отображаться следующим образом, это поможет вам скомпилировать все файлы js files и ts файлы в папке packages в папку сборки каталога, в котором вы находитесь:

Далее мы можем запустить команду jest:

npm run jest
# Equivalent to
# node ./packages/jest-cli/bin/jest.js

Здесь вы можете выполнить обработку анализа в соответствии с различными переданными параметрами, такими как:

npm run jest -h
node ./packages/jest-cli/bin/jest.js /path/test.spec.js

Он выполнит файл jest.js, а затем введет метод запуска в файл build/cli. Метод run будет анализировать различные параметры в команде. Конкретный принцип заключается в том, что библиотека yargs взаимодействует с process.argv для достижения

const importLocal = require("import-local");
if (!importLocal(__filename)) {
  if (process.env.NODE_ENV == null) {
    process.env.NODE_ENV = "test";
  }
  require("../build/cli").run();
}

шутка-конфигурация

Когда будут получены различные параметры команды, будет выполнен основной метод runCLI, который является основным методом библиотеки @jest/core -> packages/jest-core/src/cli/index.ts.

import { runCLI } from "@jest/core";
const outputStream = argv.json || argv.useStderr ? process.stderr : process.stdout;
const { results, globalConfig } = await runCLI(argv, projects);

Метод runCLI будет использовать входной параметр argv, только что проанализированный в команде, для чтения информации файла конфигурации с помощью метода readConfigs. readConfigs происходит от packages/jest-config/src/index.ts, здесь будет normalize для заполнения и инициализации некоторых настроенных по умолчанию параметров. Его параметры по умолчанию записываются в файл packages/jest-config/src/Defaults.ts. Например, если вы запускаете только один тест js, значение по умолчанию require. resolve('jest-runner') — это бегун, который запускает один тест, и он также взаимодействует с библиотекой chalk для создания outputStream для вывода содержимого на консоль.

Кстати, отмечу принцип введения шутки в модуль. Сначала require.resolve(moduleName) найдет путь к модулю и сохранит путь в конфигурации, а затем с помощью библиотеки инструментов packages/jest-util/src/requireOrImportModule The requireOrImportModule method of .ts вызовет инкапсулированный собственный метод import/reqiure для сопоставления пути в файле конфигурации для извлечения модуля.

  • Конфигурация globalConfig из argv
  • конфиги взяты из конфигурации jest.config.js
const { globalConfig, configs, hasDeprecationWarnings } = await readConfigs(
  argv,
  projects
);
if (argv.debug) {
  /*code*/
}
if (argv.showConfig) {
  /*code*/
}
if (argv.clearCache) {
  /*code*/
}
if (argv.selectProjects) {
  /*code*/
}

шутка-спешка-карта

jest-haste-map используется для получения всех файлов в проекте и зависимостей между ними. Это достигается путем просмотра вызовов import/require, извлечения их из каждого файла и создания карты, содержащей каждый файл A и его зависимости. Здесь Haste — это модульная система, используемая Facebook. У него также есть что-то под названием HasteContext, потому что у него есть HasteFS (файловая система Haste). HasteFS — это просто список файлов в системе и всех связанных с ним зависимостей. Элемент — это структура данных карты, где ключом является путь, а значением — метаданные. Сгенерированный здесь contexts будет использоваться до этапа onRunComplete.

const { contexts, hasteMapInstances } = await buildContextsAndHasteMaps(
  configs,
  globalConfig,
  outputStream
);

шутник

Метод _run10000 получит contexts в соответствии с информацией о конфигурации globalConfig и configs. contexts будет хранить информацию о конфигурации и пути каждого локального файла и т. д., а затем вызовет функцию обратного вызова onComplete, глобальную конфигурацию globalConfig и область действия contexts войдут в метод runWithoutWatch.

Затем вы войдете в метод runJest файла packages/jest-core/src/runJest.ts, где переданный contexts будет использоваться для прохождения всех модульных тестов и сохранения их в массиве.

let allTests: Array<Test> = [];
contexts.map(async (context, index) => {
  const searchSource = searchSources[index];
  const matches = await getTestPaths(
    globalConfig,
    searchSource,
    outputStream,
    changedFilesPromise && (await changedFilesPromise),
    jestHooks,
    filter
  );
  allTests = allTests.concat(matches.tests);
  return { context, matches };
});

И используйте метод Sequencer для сортировки отдельных тестов.

const Sequencer: typeof TestSequencer = await requireOrImportModule(
  globalConfig.testSequencer
);
const sequencer = new Sequencer();
allTests = await sequencer.sort(allTests);

Метод runJest вызывает ключевой метод packages/jest-core/src/TestScheduler.ts метода scheduleTests.

const results = await new TestScheduler(
  globalConfig,
  { startRun },
  testSchedulerContext
).scheduleTests(allTests, testWatcher);

Метод scheduleTests будет делать много вещей, он будет собирать contexts в allTests в contexts, собирать duration в массив timings и подписываться на четыре жизненных цикла перед выполнением всех одиночных тестов:

  • запуск тестового файла
  • тест-файл-успех
  • сбой тестового файла
  • результат теста

Затем перейдите по contexts и используйте новый пустой объект testRunners, чтобы выполнить некоторую обработку и сохранить его, что вызовет метод createScriptTransformer, предоставляемый @jest/transform, для обработки импортированных модулей.

import { createScriptTransformer } from "@jest/transform";
const transformer = await createScriptTransformer(config);
const Runner: typeof TestRunner = interopRequireDefault(
  transformer.requireAndTranspileModule(config.runner)
).default;
const runner = new Runner(this._globalConfig, {
  changedFiles: this._context?.changedFiles,
  sourcesRelatedToTestsInChangedFiles: this._context?.sourcesRelatedToTestsInChangedFiles,
});
testRunners[config.runner] = runner;

Метод scheduleTests вызовет метод runTests packages/jest-runner/src/index.ts.

async runTests(tests, watcher, onStart, onResult, onFailure, options) {
  return await (options.serial
    ? this._createInBandTestRun(tests, watcher, onStart, onResult, onFailure)
    : this._createParallelTestRun(
        tests,
        watcher,
        onStart,
        onResult,
        onFailure
      ));
}

В последнем методе _createParallelTestRun или _createInBandTestRun:

Будет метод runTestInWorker, который, как следует из названия, должен выполнить один тест в воркере.

_createInBandTestRun выполнит базовый метод runTest в packages/jest-runner/src/runTest.ts и выполнит метод runTestInternal в runJest, который подготовит много приготовлений перед выполнением одного теста. Дело в том, что глобальный метод переписывается и захватывает методы импорта и экспорта.

await this.eventEmitter.emit("test-file-start", [test]);
return runTest(
  test.path,
  this._globalConfig,
  test.context.config,
  test.context.resolver,
  this._context,
  sendMessageToJest
);

В методе runTestInternal модуль fs будет использоваться для чтения содержимого файла и помещения его в cacheFS, который можно кэшировать для последующего быстрого чтения. Например, если позже содержимое файла будет в формате json, его можно будет прочитать прямо в cacheFS. Также используйте Date.now разницу во времени для расчета затрат времени.

const testSource = fs().readFileSync(path, "utf8");
const cacheFS = new Map([[path, testSource]]);

В методе runTestInternal будет представлен packages/jest-runtime/src/index.ts, который поможет вам кэшировать и читать модули и запускать выполнение.

const runtime = new Runtime(
  config,
  environment,
  resolver,
  transformer,
  cacheFS,
  {
    changedFiles: context?.changedFiles,
    collectCoverage: globalConfig.collectCoverage,
    collectCoverageFrom: globalConfig.collectCoverageFrom,
    collectCoverageOnlyFrom: globalConfig.collectCoverageOnlyFrom,
    coverageProvider: globalConfig.coverageProvider,
    sourcesRelatedToTestsInChangedFiles: context?.sourcesRelatedToTestsInChangedFiles,
  },
  path
);

Здесь пакет @jest/console используется для перезаписи глобальной консоли. Для того, чтобы консоль однократно протестированного блока файлового кода плавно выводила результаты на терминал узла, в связке с пакетом jest-environment-node установите глобальный environment.global all Rewrite для облегчения последующих методов получения этих областей видимости в vm.

// Essentially it is rewritten using node's console to facilitate subsequent overwriting of the console method in the vm scope
testConsole = new BufferedConsole();
const environment = new TestEnvironment(config, {
  console: testConsole, // Suspected useless code
  docblockPragmas,
  testPath: path,
});
// Really rewrite the console method
setGlobal(environment.global, "console", testConsole);

runtime в основном использует эти два метода для загрузки модуля, сначала оцените, является ли он модулем ESM, если это так, используйте runtime.unstable_importModule для загрузки модуля и запуска модуля, если нет, используйте runtime.requireModule для загрузки модуля и запуска модуля.

const esm = runtime.unstable_shouldLoadAsEsm(path);
if (esm) {
  await runtime.unstable_importModule(path);
} else {
  runtime.requireModule(path);
}

шутка-цирк

Сразу после того, как testFramework в runTestInternal примет поступающую среду выполнения для вызова одного тестового файла для запуска, метод testFramework поступает из библиотеки с интересным названием packages/jest-circus/src/legacy-code-todo-rewrite /jestAdapter.ts, где legacy-code-todo-rewrite означает переписываемый устаревший код, jest-circus в основном переписывает некоторые методы глобального global, с участием этих немногих:

  • после всего
  • после каждого
  • перед всем
  • перед каждым
  • описывать
  • it
  • контрольная работа

Прежде чем вызвать одиночный тест здесь, функция jestAdapter, которая является вышеупомянутой runtime.requireModule, загрузит файл xxx.spec.js. Среда выполнения globals была предварительно задана с помощью initialize перед выполнением. And snapshotState, and rewrite beforeEach. If resetModules, clearMocks, resetMocks, restoreMocksandsetupFilesAfterEnv` будут выполнены соответственно следующие методы:

  • runtime.resetModules
  • время выполнения.clearAllMocks
  • время выполнения.resetAllMocks
  • время выполнения.restoreAllMocks
  • runtime.requireModule или runtime.unstable_importModule

После выполнения инициализации метода initialize, поскольку initialize переписал глобальные методы describe и test, все эти методы переписаны здесь, в /packages/jest-circus/src/index.ts, здесь. Обратите внимание, что в методе test есть метод dispatchSync. Это ключевой метод. Здесь копия state будет поддерживаться глобально. dispatchSync означает сохранение функций и другой информации в кодовом блоке test в state. В dispatchSync используется name в сочетании с методом eventHandler для изменения state. Эта идея очень похожа на поток данных в Redux.

const test: Global.It = () => {
  return (test = (testName, fn, timeout) => (testName, mode, fn, testFn, timeout) => {
    return dispatchSync({
      asyncError,
      fn,
      mode,
      name: "add_test",
      testName,
      timeout,
    });
  });
};

Единственный тест xxx.spec.js, то есть файл testPath, будет импортирован и выполнен после файла initialize. Обратите внимание, что этот единственный тест будет выполняться при импорте сюда, потому что файл одного теста xxx.spec.js написан в соответствии со спецификациями. Там будут блоки кода, такие как test и describe, поэтому в это время все функции обратного вызова, принятые test и describe, будут хранится в глобальном state.

const esm = runtime.unstable_shouldLoadAsEsm(testPath);
if (esm) {
  await runtime.unstable_importModule(testPath);
} else {
  runtime.requireModule(testPath);
}

время шутки

Здесь он сначала определит, является ли это модулем esm, если это так, используйте метод unstable_importModule для его импорта, в противном случае используйте метод requireModule для его импорта, в частности, он войдет в следующую функцию.

this._loadModule(localModule, from, moduleName, modulePath, options, moduleRegistry);

Логика _loadModule состоит всего из трех основных частей.

  • Оцените, является ли это файлом суффикса json, выполните readFile, чтобы прочитать текст, и используйте transformJson и JSON.parse для преобразования выходного содержимого.
  • Определите, является ли файл суффикса узла, и выполните требуемый собственный метод для импорта модуля.
  • Для файлов, которые не соответствуют двум вышеуказанным условиям, выполните модуль исполнения _execModule.

_execModule будет использовать babel для преобразования исходного кода, прочитанного fs. Это transformFile метод transform packages/jest-runtime/src/index.ts.

const transformedCode = this.transformFile(filename, options);

_execModule будет использовать метод createScriptFromCode для вызова собственного модуля vm узла для фактического выполнения js. Модуль vm принимает безопасный исходный код и использует виртуальную машину V8 с входящим контекстом для немедленного выполнения кода или отсрочки выполнения кода. Здесь вы можете принять разные области действия для выполнения одного и того же кода для вычисления разных результатов, что очень подходит для использования тестовых фреймворков. Внедренный vmContext здесь представляет собой указанную выше глобальную область перезаписи, включая afterAll, afterEach, beforeAll, beforeEach, description, it, test. Таким образом, наш единственный тестовый код получит эти методы с областью внедрения при запуске.

const vm = require("vm");
const script = new vm().Script(scriptSourceCode, option);
const filename = module.filename;
const vmContext = this._environment.getVmContext();
script.runInContext(vmContext, {
  filename,
});

Когда глобальный метод перезаписывается и state сохраняется выше, он войдет в логику функции обратного вызова, которая фактически выполняет describe, в методе run packages/jest-circus/src/run.ts, здесь Используйте метод getState, чтобы вынуть блок кода describe, затем используйте _runTestsForDescribeBlock для выполнения этой функции, затем введите метод _runTest, а затем используйте функцию ловушки до и после выполнения _callCircusHook и используйте _callCircusTest для выполнения.

const run = async (): Promise<Circus.RunResult> => {
  const { rootDescribeBlock } = getState();
  await dispatch({ name: "run_start" });
  await _runTestsForDescribeBlock(rootDescribeBlock);
  await dispatch({ name: "run_finish" });
  return makeRunResult(getState().rootDescribeBlock, getState().unhandledErrors);
};
const _runTest = async (test, parentSkipped) => {
  // beforeEach
  // test function block, testContext scope
  await _callCircusTest(test, testContext);
  // afterEach
};

Это основная позиция реализации функции хука, а также основной элемент функции Jest.

Наконец

Я надеюсь, что эта статья поможет вам понять основную реализацию и принципы среды тестирования Jest. Спасибо, что терпеливо читаете. Если статьи и заметки могут дать вам намек на помощь или вдохновение, пожалуйста, не скупитесь на свою звезду и вилку. Статьи постоянно обновляются синхронно, ваше подтверждение - моя самая большая мотивация двигаться вперед😁