Especifique el código a ejecutar antes de que ocurra cualquier configuración de Jest


El tl; dr es:

1) Cómo puedo hacer que Jest use la función nativa require para cargar todos los módulos en mis pruebas en cualquier lugar.

2) Dónde / cómo podría modificar (es decir, reemplazar con el cargador esm) https://github.com/standard-things/esm la función require en un solo lugar, antes de ejecutar cualquier prueba, por lo que todas las pruebas utilizarán el require modificado.


Me gustaría usar el esm-loader con mis archivos de prueba de Jest. Con el fin de hacerlo, tengo que parchear el requerir la función globalmente, antes de que se ejecute cualquier código de prueba, con algo como

require = require("@std/esm")(module, { esm: "js", cjs: true });

¿Cómo le digo a Jest que ejecute ese código antes de que se toque o solicite algo más?

Intenté apuntar tanto setupTestFrameworkScriptFile como una entrada de matriz setupFiles a un archivo con eso en él, pero ninguno funcionó (aunque confirmé que ambos corrían).

Alternativamente, estoy disparando estas pruebas con un script npm

"scripts": {
  "test": "jest"
}

¿Hay alguna magia CLI por la que solo puedo cargar un módulo y entonces ejecutar jest?


Edit - las opciones testEnvironment y resolver me hacen preguntarme si esto está incluso usando la función Node require real para cargar módulos, o en su lugar usando su propio cargador de módulos. Si es así me pregunto si esto es posible.

Author: Adam Rackis, 2017-09-26

3 answers

Así que este fue un poco difícil de conseguir trabajar. La solución es bastante simple, pero me llevó un tiempo hacerlo funcionar. El problema es que cada vez que se utiliza cualquier módulo en jest

  • Archivos de configuración
  • Configurar archivos de marco
  • Archivos de prueba
  • Archivos de módulo

Todos están cargados por debajo de la manera

({"Object.": function (module,exports,require,__dirname,__filename,global,jest){ / * Module code inside* / }});

Si echas un vistazo a node_modules/jest-runtime/build/index.js:495:510

const dirname = (_path || _load_path()).default.dirname(filename);
localModule.children = [];
localModule.parent = mockParentModule;
localModule.paths = this._resolver.getModulePaths(dirname);
localModule.require = this._createRequireImplementation(filename, options);

const transformedFile = this._scriptTransformer.transform(
filename,
{
  collectCoverage: this._coverageOptions.collectCoverage,
  collectCoverageFrom: this._coverageOptions.collectCoverageFrom,
  collectCoverageOnlyFrom: this._coverageOptions.collectCoverageOnlyFrom,
  isInternalModule,
  mapCoverage: this._coverageOptions.mapCoverage },

this._cacheFS[filename]);

this._createRequireImplementation(filename, options); le da a cada módulo un objeto require personalizado. Así que como tal no obtiene la función nativa requerir en absoluto, en cualquier lugar. Una vez que jest ha iniciado cada módulo cargado a partir de entonces tendrá la función personalizada require de jest.

Cuando cargamos un módulo, se llama a los métodos requireModule de jest-runtime. A continuación se muestra un extracto de la misma

  moduleRegistry[modulePath] = localModule;
  if ((_path || _load_path()).default.extname(modulePath) === '.json') {
    localModule.exports = this._environment.global.JSON.parse(
    (0, (_stripBom || _load_stripBom()).default)((_gracefulFs || _load_gracefulFs()).default.readFileSync(modulePath, 'utf8')));

  } else if ((_path || _load_path()).default.extname(modulePath) === '.node') {
    // $FlowFixMe
    localModule.exports = require(modulePath);
  } else {
    this._execModule(localModule, options);
  }

Como puede ver si la extensión del archivo es .node carga el módulo directamente, de lo contrario llama al _execModule. Esta función es el mismo código que he publicado anteriormente que hace la transformación de código

const isInternalModule = !!(options && options.isInternalModule);
const filename = localModule.filename;
const lastExecutingModulePath = this._currentlyExecutingModulePath;
this._currentlyExecutingModulePath = filename;
const origCurrExecutingManualMock = this._isCurrentlyExecutingManualMock;
this._isCurrentlyExecutingManualMock = filename;

const dirname = (_path || _load_path()).default.dirname(filename);
localModule.children = [];
localModule.parent = mockParentModule;
localModule.paths = this._resolver.getModulePaths(dirname);
localModule.require = this._createRequireImplementation(filename, options);

Ahora cuando queremos modificar la función require para nuestra prueba, necesitamos _execModule para exportar nuestro código directamente. Así que el código debe ser similar a la carga de un .node módulos

  } else if ((_path || _load_path()).default.extname(modulePath) === '.mjs') {
    // $FlowFixMe
    require = require("@std/esm")(localModule);
    localModule.exports = require(modulePath);
  } else {

Pero hacer eso significaría parchear el código, lo que queremos evitar. Así que lo que hacemos en su lugar es evitar usar el comando jest directamente, y crear nuestro propio jestload.js y corriendo eso. El código para cargar jest es simple

#!/usr/bin/env node
/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

cli = require('jest/bin/jest');

Ahora queremos modificar el _execModule antes de que se cargue la cli. Así que agregamos el siguiente código

const jestRuntime = require("jest-runtime");
oldexecModule = jestRuntime.prototype._execModule;

jestRuntime.prototype._execModule = function (localModule, options) {
    if (localModule.id.indexOf(".mjs") > 0) {
        localModule.exports = require("@std/esm")(localModule)(localModule.id);
        return localModule;
    }
    return oldexecModule.apply(this, [localModule, options]);
};

cli = require('jest/bin/jest');

Ahora es el momento de una prueba

//__test__/sum.test.js
sum = require('../sum.mjs').sum;


test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});


test('adds 2 + 3 to equal 5', () => {
  expect(sum(3, 2)).toBe(5);
});

Y un archivo sum.mjs

export function sum (x, y) { return x + y }

Ahora ejecutamos la prueba

Prueba de Broma

La solución está disponible a continuación repo

Https://github.com/tarunlalwani/jest-overriding-require-function-stackoverflow

Puede clonar y probar el solución ejecutando npm test.

 16
Author: Tarun Lalwani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-04 22:22:14

He intentado usar node -r @std/esm run.js donde ejecutar.js es solo un script que llama a jest, pero no funciona y se bloquea aquí: https://github.com/facebook/jest/blob/master/packages/jest-runtime/src/script_transformer.js#L305.

Por lo que entiendo de esta línea significa que no es posible porque jest compila el módulo usando el módulo nativo vm. Las líneas anteriores (290):

  if (willTransform) {
    const transformedSource = this.transformSource(
    filename,
    content,
    instrument,
    !!(options && options.mapCoverage));


    wrappedCode = wrap(transformedSource.code);
    sourceMapPath = transformedSource.sourceMapPath;
  } else {

Es el código llamado cuando está especificando transformaciones en su broma config.

Conclusión : hasta que no sean compatibles con esm ( y estarán bajo la extensión .mjs) no puede importar módulos es en jest sin especificar una transformación. Usted podría tratar de mono parche vm pero yo realmente aconsejaría en contra de esta opción.

Especificar una transformación de jest no es realmente tan difícil, y para los módulos es es realmente tan simple como usar babel-jest con la configuración correcta de babel:

Debajo de un paquete.json con ajustes mínimos

{
    "dependencies": {
        "babel-jest": "^21.2.0",
        "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
        "jest": "^21.2.1"
    },
    "jest": {
        "testMatch": [
            "<rootDir>/src/**/__tests__/**/*.js?(x)",
            "<rootDir>/src/**/?(*.)(spec|test).js?(x)"
        ],
        "transform": {
            "^.+\\.(js|jsx)$": "<rootDir>/node_modules/babel-jest"
        },
        "testEnvironment": "node",
        "testURL": "http://localhost",
        "moduleFileExtensions": [
            "js",
            "json"
        ]
    },
    "babel": {
        "plugins": ["babel-plugin-transform-es2015-modules-commonjs"]
    }
}
 -1
Author: adz5A,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-04 01:08:56

setupFiles funcionó para mí. Añadir esto en el paquete.json:

"jest": {
    "setupFiles": ["./my_file.js"]
  },

Https://jestjs.io/docs/en/configuration.html#setupfiles-array

 -1
Author: Yair Kukielka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-04 02:35:14