是否可以开玩笑地模拟从模块内部调用的函数?

Is it possible to mock a function called from inside module in jest?

我有一个带有两个函数的 TypeScript 模块:foo() 调用 bar()。我想模拟 bar() 并从单元测试中调用 foo() 。我试过这个:

foo.ts:

export function bar(){
  throw 'not implemented'
}

export function foo(){
  return bar();
}

foo.test.ts:

import * as foo from './foo'

test('TODO', () => {
  jest.spyOn(foo, 'bar').mockReturnValue('quux')
  expect(foo.foo()).toBe('quux')
});

但是我得到一个错误:

 FAIL  ./foo.test.ts
  ✕ TODO (4 ms)

  ● TODO

    thrown: "not implemented"

      1 | import * as foo from './foo'
      2 |
    > 3 | test('TODO', () => {
        | ^
      4 |   jest.spyOn(foo, 'bar').mockReturnValue('quux')
      5 |   expect(foo.foo()).toBe('quux')
      6 | });

      at Object.<anonymous> (foo.test.ts:3:1)
      at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13)

foo() 似乎以某种方式绕过了模拟。我怎样才能让它使用模拟而不是真正的 bar()?

修复此更改 foo() 以调用 exports.bar() 而不是 bar():

export function foo(){
  return exports.bar();
}

foo() 在您的示例中绕过了模拟,因为有两个名称空间:模块和导出对象。当对 bar() 的引用被解析时 JavaScript 并且 TypeScript 在模块命名空间中搜索它,但 Jest 只能监视导出对象而不能更改模块的内容。

一种可能的解决方案是通过导出对象显式查找要模拟的标识符,如上所示。

我在这里找到了这个方法:https://medium.com/welldone-software/jest-how-to-mock-a-function-call-inside-a-module-21c05c57a39f#576f

试试这个重构:

foo.ts:

const fn = {
  bar(): unknown | void {
    throw "not implemented";
  },

  foo() {
    return fn.bar();
  },
};

export default fn

foo.test.ts:

import functionsUnderTest from "./foo";

beforeEach(() => {
  jest.spyOn(functionsUnderTest, "bar").mockReturnValue("quux");
});

test("TODO", () => {
  expect(functionsUnderTest.foo()).toBe("quux");
});

命名空间问题已通过这种方式解决