Specify code to run before any Jest setup happens

前端 未结 3 1049
春和景丽
春和景丽 2021-02-11 23:24

The tl;dr is:

1) How can I have Jest use the native require function to load all modules in my tests anywhere.

2) Where / how would I go about modif

3条回答
  •  孤街浪徒
    2021-02-11 23:46

    So this one was a bit tough to get working. The solution is quite simple but it took me a while to get it working. The problem is that whenever you use any module in jest

    • Setup Files
    • Setup Framework Files
    • Test Files
    • Module files

    They are all loaded in below way

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

    If you have a look at 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); gives every module a custom require object. So you as such don't get the native require function at all, anywhere. Once jest has started every module loaded from then on will have jest's custom require function.

    When we load a module, the requireModule methods from the jest-runtime gets called. Below is an excerpt from the same

      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);
      }
    

    As you can see if the extension of the file is .node it loads the module directly, else it calls the _execModule. This function is the same code that I posted earlier which does the code transformation

    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);
    

    Now when we want to modify require function for our test, we need _execModule to export our code directly. So the code should be similar to loading of a .node modules

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

    But doing that would mean patching the code, which we want to avoid. So what we do instead is avoid using the jest command directly, and create our own jestload.js and running that. The code for loading jest is 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');
    

    Now we want to modify the _execModule before the cli loads. So we add below code

    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');
    

    Now time for a test

    //__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);
    });
    

    And a sum.mjs file

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

    Now we run the test

    The solution is available on below repo

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

    You can clone and test the solution by running npm test.

提交回复
热议问题