[英]How do I spyOn third party function with jest?
我在模擬第三方依賴項時遇到了麻煩。 我總是收到此錯誤:
無法監視未定義的屬性,因為它不是函數; 給出未定義
以下是此問題的詳細信息。 首先,這是我正在測試的功能:
文件: 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());
但現在我想監視查看器。
編輯:
這是我的最終解決方案。 @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
本身不夠用?
示例代碼將 ES6 import
/ export
語法與 Node module.exports
語法混合在一起...
...但基於一個看起來像這樣的庫:
庫.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
生成模塊的jest.genMockFromModule
版本並返回它。
所以例如這個:
const mock = jest.genMockFromModule('lib');
...生成一個mock
版本的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
模塊,這是本例中唯一需要的行。
這是一個解決方案:
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.