玩笑如何允许模块突变?

How does jest allow mutation of modules?

我在这里问的这个问题:

我问的是模块突变的性质。

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

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

这是怎么回事?

我想这是一个 babel 插件 运行 - 将模块转译为 CommonJS 模块?有没有关于这个的文档?

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

ES6 modules can't actually be mutated - all of their properties are treated as constants.

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

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 spylib.someFunc 应该绑定到 ES6 模块中的 someFunc


But somehow - when Jest tests modules - they can be mutated, and that's how Jest allows for mocking.

How is this happening?

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

(为了完整起见,我想 可能 可以 运行 Jest 通过使用 Nodeexperimental support for ES6 Modules 但我没试过)。


I imagine that it's a babel plugin that that's running - transpiling the module...Is there any documentation about this?

"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 进行配置,它将“正则表达式映射到转换器的路径”。


Is there a way to view the transpiled code?

是的。转换在 jest-runtime here and the output is saved to a cache here.

中完成

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

你可以通过 运行ning Jest--showConfig 选项来做到这一点,它将输出 运行ning [=16= 时使用的 config ].可以通过查看“cacheDirectory”的值找到缓存位置。

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

最后,运行 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.jscode.test.jsrequire('./lib').

得到相同的对象

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


所以是的,你完全正确。像这样的间谍(或嘲笑)之所以有效,是因为 ES6 模块被 babel 转译为 Node 模块,其方式允许它们发生变异。