Unit testing 当模块未被修改时,如何在Jest中模拟导入的命名函数

Unit testing 当模块未被修改时,如何在Jest中模拟导入的命名函数,unit-testing,ecmascript-6,jestjs,Unit Testing,Ecmascript 6,Jestjs,我正在开玩笑地测试以下模块: // myModule.js export function otherFn() { console.log('do something'); } export function testFn() { otherFn(); // do other things } 如上所示,它导出一些命名函数,重要的是testFn使用otherFn // myModule.js exports.otherFn = () => { console.log

我正在开玩笑地测试以下模块:

// myModule.js

export function otherFn() {
  console.log('do something');
}

export function testFn() {
  otherFn();

  // do other things
}
如上所示,它导出一些命名函数,重要的是
testFn
使用
otherFn

// myModule.js
exports.otherFn = () => {
  console.log('do something');
}

exports.testFn = () => {
  exports.otherFn();

  // do other things
}
在为
testFn
编写单元测试时,我想模拟
otherFn
函数,因为我不希望
otherFn
中的错误影响我对
testFn
的单元测试。我的问题是,我不确定这样做的最佳方式:

// myModule.test.js
jest.unmock('myModule');

import { testFn, otherFn } from 'myModule';

describe('test category', () => {
  it('tests something about testFn', () => {
    // I want to mock "otherFn" here but can't reassign
    // a.k.a. can't do otherFn = jest.fn()
  });
});

非常感谢您提供的任何帮助/见解。

传输的代码将不允许babel检索
otherFn()
所引用的绑定。如果使用函数expression,应该能够实现mocking
otherFn()

但正如@kentcdoffics在前面的评论中提到的,您可能不想模仿
otherFn()
。相反,只需为
otherFn()
编写一个新规范,并模拟它正在进行的任何必要调用

例如,如果
otherFn()
正在发出http请求

// myModule.js
exports.otherFn = () => {
  http.get('http://some-api.com', (res) => {
    // handle stuff
  });
};
在这里,您需要模拟
http.get
,并根据模拟的实现更新断言

// myModule.test.js
jest.mock('http', () => ({
  get: jest.fn(() => {
    console.log('test');
  }),
}));
不适用于我,我使用过:

import * as m from '../myModule';

m.otherFn = jest.fn();

在这里的第一个答案之上,您还可以使用来模拟导入的命名函数。你可以从表面上看一下这一部分 .


对于您的情况来说,一个直接的好处是您不需要更改调用函数中另一个函数的方式。

看起来我来晚了,但是是的,这是可能的

testFn
只需使用模块调用
otherFn

如果
testFn
使用模块调用
otherFn
,则可以模拟
otherFn
的模块导出,并且
testFn
将调用模拟


以下是一个工作示例:

myModule.js

从“/myModule”导入*作为myModule;//将myModule导入自身
导出函数otherFn(){
返回“原值”;
}
导出函数testFn(){
const result=myModule.otherFn();//使用模块调用otherFn
//做其他事情
返回结果;
}
myModule.test.js

import * as myModule from './myModule';

describe('test category', () => {
  let otherFnOrig;

  beforeAll(() => {
    otherFnOrig = myModule.otherFn;
    myModule.otherFn = jest.fn();
  });

  afterAll(() => {
    myModule.otherFn = otherFnOrig;
  });

  it('tests something about testFn', () => {
    // using mock to make the tests
  });
});
import*作为myModule从“/myModule”导入;
描述('测试类别',()=>{
它('测试关于testFn的某些内容',()=>{
const mock=jest.spyOn(myModule,'otherFn');//监视otherFn
mock.mockReturnValue('mocked value');//模拟返回值
expect(myModule.testFn()).toBe('mocked value');//成功
mock.mockRestore();//还原其他fn
});
});
使用
jest.requireActual()
内部
jest.mock()
返回实际模块而不是模拟模块,绕过所有关于模块是否应接收模拟实现的检查

例子 我更喜欢在您需要并在返回对象中传播的情况下使用这种简洁的用法:

//myModule.test.js
从“./myModule.js”导入{otherFn}
jest.mock('./myModule.js',()=>({
…(jest.requireActual('./myModule.js')),
otherFn:jest.fn()
}))
描述('测试类别',()=>{
它('testsomethinotherfn',()=>{
otherFn.mockReturnValue('foo')
expect(otherFn()).toBe('foo')
})
})
Jest的手动模拟文档中也引用了该方法(接近本章末尾):

为了确保手动模拟与其实际实现保持同步,可能需要在手动模拟中使用
jest.requireActual(moduleName)
来要求实际模块,并在导出之前使用模拟函数对其进行修改


我知道这是很久以前提出的问题,但我只是遇到了这种情况,最终找到了一个可行的解决方案。所以我想在这里分享一下

对于模块:

//myModule.js
导出函数otherFn(){
log('do something');
}
导出函数testFn(){
其他fn();
//做其他事情
}
您可以更改为以下内容:

//myModule.js
导出常量otherFn=()=>{
log('do something');
}
导出常量testFn=()=>{
其他fn();
//做其他事情
}
将它们导出为常量而不是函数。我相信这个问题与JavaScript中的提升有关,使用
const
可以防止这种行为

然后在您的测试中,您可以有如下内容:

import*作为myModule从“myModule”导入;
描述(“…”,()=>{
jest.spyOn(myModule,'otherFn').mockReturnValue('what your want return');
//或
myModule.otherFn=jest.fn(()=>{
//您的模拟实现
});
});

您的模拟现在应该可以正常工作了。

我在这里找到了一系列答案,解决了我的问题:

myModule.js

myModule.test.js

import * as myModule from './myModule';

describe('test category', () => {
  let otherFnOrig;

  beforeAll(() => {
    otherFnOrig = myModule.otherFn;
    myModule.otherFn = jest.fn();
  });

  afterAll(() => {
    myModule.otherFn = otherFnOrig;
  });

  it('tests something about testFn', () => {
    // using mock to make the tests
  });
});
基于此,我可以在TypeScript中使用相同的方法。此外,使用它可以仅在测试文件的某些特定测试中模拟模块功能,并为每个模块提供单独的模拟实现

src/module.ts

import*作为模块从“./module”导入;
函数foo():字符串{
返回`foo${module.bar()}`;
}
函数栏():字符串{
返回“bar”;
}
出口{foo,bar};
测试/模块.test.ts

从“/helpers”导入{mockModulePartially};
从“../src/module”导入*作为模块;
const{foo}=模块;
描述('测试套件',()=>{
beforeach(函数(){
jest.reset模块();
});
它('不模拟条1',async()=>{
expect(foo()).toEqual('foobar');
});
它('mock bar',async()=>{
mockmodule部分('../src/module',()=>({
bar:jest.fn().mockImplementation(()=>“bar”)
}));
常量模块=等待导入('../src/module');
const{foo}=模块;
expect(foo()).toEqual('fooBAR');
});
它('不模拟条2',async()=>{
expect(foo()).toEqual('foobar');
});
});
test/helpers.ts

import * as myModule from './myModule'; // import myModule into itself export function otherFn() { return 'original value'; } export function testFn() { const result = myModule.otherFn(); // call otherFn using the module // do other things return result; }
import * as myModule from './myModule';

describe('test category', () => {
  let otherFnOrig;

  beforeAll(() => {
    otherFnOrig = myModule.otherFn;
    myModule.otherFn = jest.fn();
  });

  afterAll(() => {
    myModule.otherFn = otherFnOrig;
  });

  it('tests something about testFn', () => {
    // using mock to make the tests
  });
});