我想测试我的一个ES6模块以特定的方式调用另一个ES6模块。对贾斯敏来说,这非常简单

应用程序代码:

// myModule.js
import dependency from './dependency';

export default (x) => {
  dependency.doSomething(x * 2);
}

测试代码:

//myModule-test.js
import myModule from '../myModule';
import dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    spyOn(dependency, 'doSomething');

    myModule(2);

    expect(dependency.doSomething).toHaveBeenCalledWith(4);
  });
});

与Jest对应的是什么?我觉得这是一件很简单的事情,但我一直在努力想弄清楚。

我最接近的方法是用require替换导入,并将它们移动到测试/函数中。这两件事我都不想做。

// myModule.js
export default (x) => {
  const dependency = require('./dependency'); // Yuck
  dependency.doSomething(x * 2);
}

//myModule-test.js
describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    jest.mock('../dependency');

    myModule(2);

    const dependency = require('../dependency'); // Also yuck
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

作为奖励,我希望在dependency.js中的函数是默认导出时使整个事情正常工作。但是,我知道监视默认导出在Jasmine中不起作用(或者至少我永远无法让它起作用),所以我对在Jest中也能做到这一点不抱希望。


当前回答

你必须模拟模块和设置间谍自己:

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency', () => ({
  doSomething: jest.fn()
}))

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

其他回答

这里的答案似乎都不适合我(原始函数总是被导入,而不是mock),而且Jest中的ESM支持似乎仍在进行中。

在发现这条注释之后,我发现joke .mock()实际上不能与常规导入一起工作,因为导入总是在mock之前运行(现在也正式记录了这一点)。因此,我使用await import()导入我的依赖项。这甚至适用于顶级的等待,所以我只需要调整我的导入:

import { describe, expect, it, jest } from '@jest/globals';

jest.unstable_mockModule('../dependency', () => ({
  doSomething: jest.fn()
}));

const myModule = await import('../myModule');
const dependency = await import('../dependency');

describe('myModule', async () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

快进到2020年,我发现这篇博客文章是解决方案:Jest模拟默认和命名导出

仅使用ES6模块语法:

// esModule.js
export default 'defaultExport';
export const namedExport = () => {};

// esModule.test.js
jest.mock('./esModule', () => ({
  __esModule: true, // this property makes it work
  default: 'mockedDefaultExport',
  namedExport: jest.fn(),
}));

import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function

还有一件事你需要知道(我花了一段时间才弄明白),你不能在测试中调用joke .mock();您必须在模块的顶层调用它。但是,如果您想为不同的测试设置不同的mock,则可以在单独的测试中调用mockImplementation()。

我对@cam-jackson的原始答案做了一些修改,副作用已经消失了。我使用lodash库深度克隆测试对象,然后对该对象进行任何修改。但是要注意克隆沉重的对象会对测试性能和测试速度产生负面影响。

objectUndertest.js

const objectUnderTest = {};
export default objectUnderTest;

objectUnderTest.myFunctionUnterTest = () => {
  return "this is original function";
};

objectUndertest.test.js

import _ from "lodash";
import objectUndertest from "./objectUndertest.js";

describe("objectUndertest", () => {
  let mockObject = objectUndertest;

  beforeEach(() => {
    mockObject = _.cloneDeep(objectUndertest);
  });

  test("test function", () => {
    mockObject.myFunctionUnterTest = () => {
      return "this is mocked function.";
    };

    expect(mockObject.myFunctionUnterTest()).toBe("this is mocked function.");
  });
});

使用Jest模拟ES6依赖模块的默认导出:

import myModule from '../myModule';
import dependency from '../dependency';

jest.mock('../dependency');

// If necessary, you can place a mock implementation like this:
dependency.mockImplementation(() => 42);

describe('myModule', () => {
  it('calls the dependency once with double the input', () => {
    myModule(2);

    expect(dependency).toHaveBeenCalledTimes(1);
    expect(dependency).toHaveBeenCalledWith(4);
  });
});

其他的选择对我的案子不起作用。

为Andreas的回答补充更多内容。我在ES6代码中遇到了同样的问题,但我不想改变导入。那看起来很俗气。所以我这样做了:

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
  });
});

并在“__ mocks __”文件夹中与“file dependency.js”并行添加了file dependency.js。这对我很管用。此外,这让我可以选择从模拟实现中返回合适的数据。确保为要模拟的模块提供了正确的路径。