【问题标题】:How does jest allow mutation of modules?jest 如何允许模块的突变?
【发布时间】:2019-06-18 21:09:42
【问题描述】:

在我在这里问的这个问题中:

Why does mutating a module update the reference if calling that module from another module, but not if calling from itself?

我在询问模块突变的性质。

然而事实证明,ES6 模块实际上不能被改变——它们的所有属性都被视为常量。 (See this answer)

但不知何故——当 Jest 测试模块时——它们可以被变异,这就是 Jest 允许模拟的方式。

这是怎么回事?

我想这是一个正在运行的 babel 插件 - 将模块转换为 CommonJS 模块?有这方面的文档吗?

有没有办法查看转码后的代码?

【问题讨论】:

    标签: jestjs es6-modules transpiler babel-jest


    【解决方案1】:

    ES6 模块实际上不能被改变 - 它们的所有属性都被视为常量。

    有趣。你是对的,即使是这么简单的事情:

    import * as lib from "./lib";  // import an ES6 module
    const spy = jest.spyOn(lib, 'someFunc');  // spy on someFunc
    

    ...技术上不应该被允许,因为 jest.spyOn replaces the method on the object with a spy 和 lib.someFunc 应该是 ES6 模块中 someFunc 的绑定。


    但不知何故——当 Jest 测试模块时——它们可以被变异,这就是 Jest 允许模拟的方式。

    这是怎么回事?

    它们只能被变异,因为 Jest 实际上并没有使用 ES6 模块。

    (为了完整性,我想可能可以通过使用Node的experimental support for ES6 Modules使用实际的ES6模块运行Jest,但我没有尝试过)。


    我想它是一个正在运行的 babel 插件 - 转换模块...有关于此的任何文档吗?

    "babel-jest is automatically installed when installing Jest and will automatically transform files if a babel configuration exists in your project. To avoid this behavior, you can explicitly reset the transform configuration option".

    所以默认情况下Jest 将使用babel-jest,它使用babel 转换源代码(并执行一些其他操作,例如hoisting calls to jest.mock)。

    请注意,Jest 也可以使用 transform 进行配置,它将“正则表达式映射到转换器的路径”。


    有没有办法查看转译后的代码?

    是的。转换在jest-runtimehere 中完成,输出保存到缓存here。

    查看转译代码最简单的方法是查看缓存。

    您可以通过运行带有--showConfig 选项的Jest 来做到这一点,该选项将输出运行Jest 时使用的config。可以通过查看“cacheDirectory”的值找到缓存位置。

    然后使用--clearCache 选项运行Jest 以清除缓存。

    最后,正常运行Jest,缓存目录将包含你项目的转译代码。


    示例

    最新的Jest (v24) 将转译此代码:

    // lib.js
    export const someFunc = () => 1;
    
    
    // code.js
    import { someFunc } from './lib';
    export const func = () => someFunc() + 1;
    
    
    // code.test.js
    import { func } from './code';
    import * as lib from './lib';
    
    test('func', () => {
      const spy = jest.spyOn(lib, 'someFunc');
      func();
      expect(spy).toHaveBeenCalled();  // SUCCESS
    });
    

    ...到这个:

    // lib.js
    "use strict";
    Object.defineProperty(exports, "__esModule", {
      value: true
    });
    exports.someFunc = void 0;
    const someFunc = () => 1;
    exports.someFunc = someFunc;
    
    
    // code.js
    "use strict";
    Object.defineProperty(exports, "__esModule", {
      value: true
    });
    exports.func = void 0;
    var _lib = require("./lib");
    const func = () => (0, _lib.someFunc)() + 1;
    exports.func = func;
    
    
    // code.test.js
    "use strict";
    var _code = require("./code");
    var lib = _interopRequireWildcard(require("./lib"));
    function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
    test('func', () => {
      const spy = jest.spyOn(lib, 'someFunc');
      (0, _code.func)();
      expect(spy).toHaveBeenCalled(); // SUCCESS
    });
    

    import * as lib from 'lib'; 行由 _interopRequireWildcard 处理,它在后台使用 require。

    每次调用 require "will get exactly the same object returned, if it would resolve to the same file" 所以 code.js 和 code.test.js 从 require('./lib') 获取相同的对象。

    someFunc 被导出为 exports.someFunc,这允许它被重新分配。


    所以是的,你完全正确。像这样的间谍(或模拟)之所以有效,是因为 ES6 模块被 babel 转换为 Node 模块,从而允许它们发生变异。

    【讨论】:

      猜你喜欢
      • 2019-08-29
      • 2019-10-19
      • 2018-06-12
      • 2012-09-17
      • 1970-01-01
      • 2019-10-01
      • 2017-10-13
      • 2020-03-21
      • 2019-06-04
      相关资源
      最近更新 更多