如何使用 jest 监视第三方功能?

cal*_*r47 3 javascript unit-testing jestjs

我在嘲笑第三方依赖时遇到困难。我总是收到这个错误:

无法监视未定义的属性,因为它不是函数;未定义给定代替

以下是这个问题的详细信息。首先,这是我正在测试的功能:

文件: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.',
            );
        }
    },
}
Run Code Online (Sandbox Code Playgroud)

查看我的第三方源代码,Viewer非常简单,如下所示:


function Viewer(){
 // Doing things
}

Viewer.prototype.foo = function(){

}

module.exports = Viewer;
Run Code Online (Sandbox Code Playgroud)

最后,这是我的测试。

文件:/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();
        });
    });
});
Run Code Online (Sandbox Code Playgroud)

我如何模拟和监视查看器本身?

我过去曾这样做过:

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')
}
Run Code Online (Sandbox Code Playgroud)

然后里面./path/Viewer就是我之前在上面描述的。

我的解决方案最终如下所示:

module.exports = {
  Viewer: require('./path/Viewer'),
  Foo: require('./foo_path/Foo'),
  Bar: require('./bar_path/Bar')
}
Run Code Online (Sandbox Code Playgroud)

@brian-lives-outdoors 我不明白的是,如果我注释掉jest.mock('lib');上面的行,它就不起作用......为什么?

为什么genMockFromModule它本身还不够呢?

Bri*_*ams 5

示例代码混合了 ES6 import/export语法和 Nodemodule.exports语法...

...但是基于一个如下所示的库:

库.js

function Viewer() { }

Viewer.prototype.foo = function () { }

module.exports = Viewer;
Run Code Online (Sandbox Code Playgroud)

...它会像这样使用:

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);
Run Code Online (Sandbox Code Playgroud)

...并且要监视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!
});
Run Code Online (Sandbox Code Playgroud)

请注意,如果该库是 ES6 库,那么您可以default直接监视导出,如下所示:

import * as lib from './lib';

const spy = jest.spyOn(lib, 'default');  // <= spy on the default export
Run Code Online (Sandbox Code Playgroud)

...但是由于 Babel 处理 ES6 和非 ES6 代码之间互操作的方式,如果库不是 ES6,这种方法就不起作用。


编辑:回答后续问题

jest.genMockFromModule生成模块的模拟版本并返回它。

例如:

const mock = jest.genMockFromModule('lib');
Run Code Online (Sandbox Code Playgroud)

...生成 的模拟版本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
Run Code Online (Sandbox Code Playgroud)

在您的最终解决方案中,您有以下两行:

jest.genMockFromModule('lib');
jest.mock('lib');
Run Code Online (Sandbox Code Playgroud)

这行:

jest.genMockFromModule('lib');
Run Code Online (Sandbox Code Playgroud)

...实际上并没有做任何事情,因为它正在生成模块的模拟,但返回的模拟没有用于任何用途。

这行:

jest.mock('lib');
Run Code Online (Sandbox Code Playgroud)

...告诉Jest自动模拟lib模块,这是本例中唯一需要的行。