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 模块,从而允许它们发生变异。