jest 如何允许修改模块? [英] How does jest allow mutation of modules?

查看:23
本文介绍了jest 如何允许修改模块?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

为什么如果从另一个模块调用该模块,而不是从自身调用,则改变模块会更新引用吗?

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

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

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

这是怎么回事?

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

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

解决方案

<块引用>

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

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

import * as lib from "./lib";//导入一个 ES6 模块const spy = jest.spyOn(lib, 'someFunc');//监视 someFunc

...技术上不应该被允许,因为 jest.spyOn 用间谍替换对象上的方法lib.someFunc 应该绑定到 someFunc 在 ES6 模块中.


<块引用>

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

这是怎么回事?

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

(我猜为了完整性,可能通过使用 Node对 ES6 模块的实验支持,但我还没有尝试过).


<块引用>

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

"babel-jest 会在以下情况下自动安装安装 Jest,如果你的项目中存在 babel 配置,它会自动转换文件.为避免这种行为,您可以显式重置 transform 配置选项".

所以默认情况下 Jest 将使用 babel-jest 它使用 babel 转译源代码(并做一些其他的事情,比如 提升对 jest 的调用.mock).

请注意,Jest 也可以使用 transform 将正则表达式映射到转换器的路径".


<块引用>

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

是的.转换在 jest-runtime here 并将输出保存到缓存 这里.

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

您可以通过运行 Jest--showConfig 选项将输出运行 Jest 时使用的 config.可以通过查看cacheDirectory"的值找到缓存位置.

然后使用--clearCache运行Jestcode> 清除缓存的选项.

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


示例

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

//lib.jsexport const someFunc = () =>1;//代码.js从 './lib' 导入 { someFunc };导出 const func = () =>someFunc() + 1;//代码.test.js从'./code' 导入 { func };import * as lib from './lib';test('func', () => {const spy = jest.spyOn(lib, 'someFunc');功能();期望(间谍).toHaveBeenCalled();//成功});

...至此:

//lib.js严格使用";Object.defineProperty(exports, "__esModule", {值:真});export.someFunc = void 0;const someFunc = () =>1;出口.someFunc = someFunc;//代码.js严格使用";Object.defineProperty(exports, "__esModule", {值:真});export.func = void 0;var _lib = require("./lib");const func = () =>(0, _lib.someFunc)() + 1;出口.func = func;//代码.test.js严格使用";var _code = require("./code");var lib = _interopRequireWildcard(require("./lib"));函数 _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;返回新对象;} }test('func', () => {const spy = jest.spyOn(lib, 'someFunc');(0, _code.func)();期望(间谍).toHaveBeenCalled();//成功});

import * as lib from 'lib'; 行由 _interopRequireWildcard 处理,它在底层使用 require.

每次调用 require "都会得到返回相同的对象,如果它会解析为相同的文件,所以 code.jscode.test.js 获取相同的对象>require('./lib').

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


是的,你说得对.像这样的窥探(或模拟)之所以有效,是因为 ES6 模块被 babel 转换成 Node 模块,以允许它们发生变异.

In this question that I asked here:

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

I'm asking about the nature of module mutation.

However as it it turns out, ES6 modules can't actually be mutated - all of their properties are treated as constants. (See this answer)

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

How is this happening?

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

Is there a way to view the transpiled code?

解决方案

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

Interesting. You're right, even something as simple as this:

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

...technically shouldn't be allowed since jest.spyOn replaces the method on the object with a spy and lib.someFunc should be a binding to someFunc in the ES6 module.


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

How is this happening?

They can only be mutated because Jest isn't actually using ES6 modules.

(I guess for completeness it might be possible to run Jest using actual ES6 modules by using Node's experimental support for ES6 Modules but I haven't tried).


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".

So by default Jest will use babel-jest which transpiles the source code using babel (and does a few other things like hoisting calls to jest.mock).

Note that Jest can be also be configured using transform which maps "regular expressions to paths to transformers".


Is there a way to view the transpiled code?

Yes. Transformations are done in jest-runtime here and the output is saved to a cache here.

The easiest way to look at the transpiled code is to view the cache.

You can do that by running Jest with the --showConfig option which will output the config used when running Jest. The cache location can be found by looking at the value of "cacheDirectory".

Then run Jest with the --clearCache option to clear out the cache.

Finally, run Jest normally and the cache directory will contain the transpiled code for your project.


Example

The latest Jest (v24) will transpile this code:

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

...to this:

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

The import * as lib from 'lib'; line gets handled by _interopRequireWildcard which uses require under the hood.

Every call to require "will get exactly the same object returned, if it would resolve to the same file" so code.js and code.test.js are getting the same object from require('./lib').

someFunc is exported as exports.someFunc which allows it to be reassigned.


So yes, you're exactly right. Spying (or mocking) like this only works because the ES6 modules are getting transpiled by babel into Node modules in a way that allows them to be mutated.

这篇关于jest 如何允许修改模块?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆