我在嘲笑第三方依赖方面遇到麻烦。我总是收到此错误:
无法监视未定义的属性,因为它不是一个函数; 未给定
以下是此问题的详细信息。首先,这是我正在测试的功能:
文件:src/js/mp_wrapper.js
import { Viewer } from 'third-party';
module.exports = {
createViewer: container => {
if (util.isElement(container)) {
return new Viewer(container);
} else {
throw new Error(
'Invalid Element when attempting to create underlying viewer.',
);
}
},
}
看看我的第三方的源代码,Viewer
非常简单,看起来像这样:
function Viewer(){
// Doing things
}
Viewer.prototype.foo = function(){
}
module.exports = Viewer;
最后,这是我的测试。
文件:/tests/mp_wrapper.spec.js
import { Viewer } from 'third-party`;
import mp_wrapper from '../src/js/mp_wrapper';
describe('mp_wrapper', () => {
describe('createViewer', () => {
test('returns a new instance of the Viewer class', () => {
const spy = jest.spyOn(Viewer).mockImplementation(() => jest.fn());
// It fails on the line above... -> "Cannot spy the undefined property because it is not a function; undefined given instead"
const testElement = document.createElement(testElement);
let viewer = mp_wrapper.createViewer(testElement);
expect(spy).toHaveBeenCalled();
expect(viewer).toBeInstancecOf(Viewer);
spy.mockRestore();
});
});
});
如何模拟和监视Viewer本身?
我过去曾经这样做过:
const spy = jest.spyOn(Viewer.prototype, 'foo').mockImplementation(() => jest.fn());
我也尝试过default
,但没有运气:
const spy = jest.spyOn(Viewer, 'default').mockImplementation(() => jest.fn());
但是现在我想监视Viewer。
编辑:
这是我的最终解决方案。 @ brian-lives-outdoors答案是正确的,但是我没有准确描述我的问题。我尝试模拟的第三方库稍微复杂一点,因为它导出了包含多个构造函数的模块。看起来像这样:
module.exports = {
Viewer: require('./path/Viewer'),
Foo: require('./foo_path/Foo'),
Bar: require('./bar_path/Bar')
}
然后./path/Viewer
中的内容与我之前所述的相同。
这是我的解决方案最终的样子:
import { Viewer } from 'lib';
import mp_wrapper from '../src/js/mp_wrapper';
jest.genMockFromModule('lib');
jest.mock('lib');
describe('mp_wrapper', () => {
describe('createViewer', () => {
test('returns a new instance of the Viewer class and sets the local _viewer property', () => {
const testContainer = document.createElement('div');
const viewer = mp_wrapper.createViewer(testContainer);
expect(Viewer).toHaveBeenCalledWith(testContainer); // Success!
expect(viewer).toBeInstanceOf(Viewer); // Success!
});
});
});
@ brian-lives-outdoors我不明白的是,如果我在上面的行jest.mock('lib');
中注释掉,那是行不通的……为什么?
genMockFromModule
本身为什么还不够?
答案 0 :(得分:2)
示例代码将ES6 import
/ export
语法与Node module.exports
语法混合在一起。
...但是基于如下所示的库:
lib.js
function Viewer() { }
Viewer.prototype.foo = function () { }
module.exports = Viewer;
...它的用法如下:
mp_wrapper.js
import Viewer from './lib'; // <= Babel allows Viewer to be used like an ES6 default export
export const createViewer = container => new Viewer(container);
...并监视Viewer
,您需要在测试中模拟整个库:
mp_wrapper.spec.js
import Viewer from './lib';
import { createViewer } from './mp_wrapper';
jest.mock('./lib', () => jest.fn()); // <= mock the library
test('returns a new instance of the Viewer class', () => {
const viewer = createViewer('the container');
expect(Viewer).toHaveBeenCalledWith('the container'); // Success!
expect(viewer).toBeInstanceOf(Viewer); // Success!
});
请注意,如果该库是ES6库,则可以像这样直接监视default
导出:
import * as lib from './lib';
const spy = jest.spyOn(lib, 'default'); // <= spy on the default export
...但是由于Babel处理ES6和非ES6代码之间的互操作的方式,如果库不是ES6,则此方法不起作用。
编辑:回答后续问题
jest.genMockFromModule
生成模块的模拟版本并返回。
例如:
const mock = jest.genMockFromModule('lib');
...生成lib
的模拟版本并将其分配给mock
。请注意,这并非表示在测试期间需要lib
时将返回该模拟。
jest.genMockFromModule
在创建手动模拟时非常有用:
__ mocks __ / lib.js
const lib = jest.genMockFromModule('lib'); // <= generate a mock of the module
lib.someFunc.mockReturnValue('some value'); // <= modify it
module.exports = lib; // <= export the modified mock
在最终解决方案中,您有以下两行:
jest.genMockFromModule('lib');
jest.mock('lib');
此行:
jest.genMockFromModule('lib');
...实际上不执行任何操作,因为它正在生成模块的模拟,但返回的模拟并未用于任何操作。
此行:
jest.mock('lib');
...告诉Jest
自动模拟lib
模块,在这种情况下,这是唯一需要的行。
答案 1 :(得分:1)
这是一个解决方案:
util.js
const util = {
isElement() {}
};
module.exports = util;
View.js
,第三方模块:
function Viewer() {
// Doing things
console.log('new viewer instance');
}
Viewer.prototype.foo = function() {};
module.exports = { Viewer };
my_wrapper.js
:
const { Viewer } = require('./viewer');
const util = require('./util');
module.exports = {
createViewer: container => {
if (util.isElement(container)) {
return new Viewer(container);
} else {
throw new Error('Invalid Element when attempting to create underlying viewer.');
}
}
};
单元测试:
const { Viewer } = require('./viewer');
const my_wrapper = require('./');
const util = require('./util');
jest.mock('./viewer', () => {
return {
Viewer: jest.fn()
};
});
describe('mp_wrapper', () => {
beforeEach(() => {
jest.resetAllMocks();
});
describe('createViewer', () => {
it('t1', () => {
util.isElement = jest.fn().mockReturnValueOnce(true);
let viewer = my_wrapper.createViewer('el');
expect(util.isElement).toBeCalledWith('el');
expect(viewer).toBeInstanceOf(Viewer);
});
it('t2', () => {
util.isElement = jest.fn().mockReturnValueOnce(false);
expect(() => my_wrapper.createViewer('el')).toThrowError(
new Error('Invalid Element when attempting to create underlying viewer.')
);
expect(Viewer).not.toBeCalled();
});
});
});
单元测试结果:
PASS src/stackoverflow/57712713/index.spec.js
mp_wrapper
createViewer
✓ t1 (6ms)
✓ t2 (5ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 50 | 100 | |
index.js | 100 | 100 | 100 | 100 | |
util.js | 100 | 100 | 0 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 4.134s, estimated 9s