全局 Jest SpyOn 函数不调用原始函数

lub*_*bro 10 javascript testing typescript jestjs

我希望有人可以帮助我理解 js 原型和jest.spOn().

我有一个小示例:文件中的示例类TestObj.ts

export default class TestObj {
  foo() {
    // Do Something e.g.
    console.log("Hello World!");
  }
}
Run Code Online (Sandbox Code Playgroud)

以下示例测试用例成功,但console.log从未执行。

import TestObj from './TestObj';

const spyObj = jest.spyOn(TestObj.prototype, 'foo');
test('debug test', () => {
  const obj = new TestObj();
  obj.foo();
  expect(spyObj).toHaveBeenCalled();
});
Run Code Online (Sandbox Code Playgroud)

如果我将示例测试用例更改为以下内容,则测试会成功并且该console.log语句将按预期调用。

import TestObj from './TestObj';

test('debug test', () => {
  const spyObj = jest.spyOn(TestObj.prototype, 'foo');
  const obj = new TestObj();
  obj.foo();
  expect(spyObj).toHaveBeenCalled();
});
Run Code Online (Sandbox Code Playgroud)

知道为什么使用全局间谍变量的版本不能按预期工作吗?


编辑:

它似乎与原型无关。对于没有任何类的函数也存在同样的问题,将第一个代码片段 ( TestObj.ts) 编辑为:

export foo() {
  // Do Something e.g.
  console.log("Hello World!");
};
Run Code Online (Sandbox Code Playgroud)

我们在更新的第二个片段中收到了同样的问题。(测试成功,但从未到达控制台日志。)

import * as testlib from './TestObj';

const spyObj = jest.spyOn(testlib, 'foo');
test('debug test', () => {
  testlib.foo();
  expect(spyObj).toHaveBeenCalled();
});
Run Code Online (Sandbox Code Playgroud)

但是,如果我们将第二个代码片段更新为以下内容,则测试会成功并执行控制台日志:

import * as testlib from './TestObj';

const spyObj: jest.SpyInstance;

beforeEach(() => {
  spyObj = jest.spyOn(testlib, 'foo');
});
test('debug test', () => {
  testlib.foo();
  expect(spyObj).toHaveBeenCalled();
});
Run Code Online (Sandbox Code Playgroud)

但是我仍然不知道为什么会发现这个问题。

lub*_*bro 6

谁遇到过这篇文章,

问题解释

我做了很多研究(try&error、mdn、jest manpage 和很多媒体文章),我想我找到了奇怪行为的原因。要理解这个问题,了解以下几点很重要:

  • 1:JS原型是全局变量,每个对应类型的对象都依赖。
  • 2:Jest 不会在每次测试后重置全局变量,之前或在任何测试中对全局变量所做的更改将保留在整个测试套件(文件)中。
  • 3:笑话监视函数实际上是指定函数的模型,并有一个调用该函数本身的实现。例如: jest.SpyOn(TestObj.prototype, 'foo');实际上实现为:TestObj.prototype.foo = new jest.fn().mockImplementation(()=>{original_TestObj.prototype.foo()}); 这意味着监视类原型的函数实际上是在更改全局变量。
  • 4:根据您的笑话配置,可以在每次测试之前将模型函数重置为默认值。但要小心, for 的默认函数spyOn似乎与它本身相同jest.fn(),是一个空实现,这意味着模型仍然可以调用,但没有代码,尤其是不执行原始实现。

解决方案

  • 如果您希望测试用例彼此独立,请避免更改全局变量。
  • 在测试用例中,避免监视原型,如果您仅在单个测试中需要监视,请尝试监视本地对象,例如:
test('should foo', () => {
  const testObj = new TestObj();
  const spyOnFn = jest.spyOn(testObj, 'foo');

  // Do anything 

  expect(spyOnFn).to//Have been anything
});
Run Code Online (Sandbox Code Playgroud)
  • 如果需要spyOn在多个测试中实现相同的函数,请尝试为测试创建一个全局变量,但在 jest 的每个功能之前使用来设置 Spy。此功能在所有模拟重置后执行(如果启用)。例如:
let spyOnFunction1: jest.SpyInstance;

beforeEach(()=> {
  spyOnFunction1 = jest.spyOn(TestObj.prototype, 'foo');
});
Run Code Online (Sandbox Code Playgroud)