简体   繁体   English

玩笑:存根 ESM 模块的命名导出的标准方法

[英]Jest: Standard way to stub named exports of ESM modules

This question is not specific to Jest as it applies to all testing libraries with stubbing capabilities.这个问题并不特定于 Jest,因为它适用于所有具有存根功能的测试库。

ESM modules have immutable named and default exports, which means this is no longer valid: ESM 模块具有不可变的命名和默认导出,这意味着这不再有效:

// @filename foo.mjs
export function foo() { ... }

// @filename foo.test.mjs
import * as foo from './foo.mjs'
// Causes runtime error because named export "foo" is immutable
jest.spyOn(foo, 'foo')

What is the current "standard" way to spy/mock named exports with ESM modules? 当前使用 ESM 模块监视/模拟命名导出的“标准”方法是什么?

Potential solutions潜在的解决方案

Delegate to mutable object委托给可变的 object

// @filename foo.mjs
export function foo() {
  return _private.foo()
}
export const _private = {
  foo: () => { ... }
}

// @filename foo.test.mjs
import { _private } from './foo.mjs'
jest.spyOn(_private, 'foo')

Wrap function in proxy在代理中包装 function

// @filename proxy.mjs
export function proxy(fn) {
  const functionProxy = function (...args) {
    return functionProxy._original(...args)
  }
  functionProxy._original = fn
  return functionProxy
}

// @filename foo.mjs
import { proxy } from './proxy.mjs'
export const foo = proxy(() => { ... })

// @filename foo.test.mjs
import { foo } from './foo.mjs'
jest.spyOn(foo, '_original')

Not really supported by Jest Jest 并不真正支持

The standard approach to mock parts of other modules is to use jest.mock , like so:模拟其他模块部分的标准方法是使用jest.mock ,如下所示:

import * as foo from './foo.mjs'

jest.mock('./foo.mjs', () => ({
  foo: () => {
    console.log(`I AM FAKE FOO`)
    return 4
  }
}))

In a CommonJS environment, jest.mock hijacks the require function so that dependencies loaded by the code under test will be replaced with your mocks as desired.在 CommonJS 环境中, jest.mock劫持require function 以便测试代码加载的依赖项将根据需要替换为您的模拟。

Unfortunately, Jest hasn't yet figured out how to do this in an ES6 module environment.不幸的是,Jest 还没有弄清楚如何在 ES6 模块环境中做到这一点。 From Jest's mocking page :来自Jest 的 mocking 页面

Please note that we currently don't support jest.mock in a clean way in ESM, but that is something we intend to add proper support for in the future.请注意,我们目前在 ESM 中不以干净的方式支持jest.mock ,但我们打算在未来添加适当的支持。 Follow this issue for updates.请关注此问题以获取更新。

It seems like the bottom line is that you will not be able to do what you're trying to do with Jest.看起来底线是你将无法用 Jest 做你想做的事情。


You say:你说:

This question is not specific to Jest as it applies to all testing libraries with stubbing capabilities.这个问题并不特定于 Jest,因为它适用于所有具有存根功能的测试库。

... but, like it not, your question is specific to Jest. ...但是,不喜欢,您的问题Jest 特有的。 Testing libraries like Jest accomplish their work by doing strange things, not by leveraging existing language features.像 Jest 这样的测试库通过做一些奇怪的事情来完成他们的工作,而不是利用现有的语言特性。 That means there isn't some arcane ES6 capability that they all use that you could learn about here.这意味着没有一些他们都使用的神秘 ES6 功能,您可以在此处了解。 Each of them uses an invented solution, and as far as I'm aware they all work by modifying the execution environment.他们每个人都使用发明的解决方案,据我所知,他们都通过修改执行环境来工作。

According the ES specification a module namespace object has:根据 ES 规范,模块命名空间 object具有:

Each such property has the attributes { [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: false }.

spyOn method try to reassign (if a property is an own property) or add a new one (if a property in a proto object) - https://github.com/facebook/jest/blob/main/packages/jest-mock/src/index.ts#L1112 spyOn 方法尝试重新分配(如果属性是自己的属性)或添加新的(如果是原始对象中的属性) - https://github.com/facebook/jest/blob/main/packages/jest-mock /src/index.ts#L1112

In spite of a writable attribute you can not reassign a property of namespace object(special nature of namespace object) or add a new one as namespace object is not extensible.尽管具有可写属性,但您不能重新分配命名空间对象的属性(命名空间对象的特殊性质)或添加新属性,因为命名空间 object 不可扩展。

But the fact that a property is writable allows to apply a next trick:但是属性是可写的这一事实允许应用下一个技巧:

// @filename foo.mjs
export function foo() { ... }

// @filename foo.test.mjs
import * as foo from './foo.mjs'
const p_foo = Object.create(foo)
// Do not cause runtime error because spyOn copies a method from proto in object itself 
jest.spyOn(p_foo, 'foo')

Honestly I have never used that trick, but I see here a plus as no need to updated existing source code, just add an additional wrapper for a namespace object.老实说,我从来没有使用过这个技巧,但我认为这里有一个优点,因为不需要更新现有的源代码,只需为命名空间 object 添加一个额外的包装器。 But that approach is coupled with info from spyOn method implementation.但这种方法与来自 spyOn 方法实现的信息相结合。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM