admin管理员组

文章数量:1316020

I am having trouble mocking a third party dependency. I'm always recieving this error:

Cannot spy the undefined property because it is not a function; undefined given instead

Here are the details of this issue. First, this is the function I am testing:

File: 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.',
            );
        }
    },
}

Looking at the source code for my third-party, Viewer is very simple and looks like this:


function Viewer(){
 // Doing things
}

Viewer.prototype.foo = function(){

}

module.exports = Viewer;

And finally, here is my test.

File: /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();
        });
    });
});

How can I mock & spy on Viewer itself?

I've done this in the past:

const spy = jest.spyOn(Viewer.prototype, 'foo').mockImplementation(() => jest.fn());

I've also tried default with no luck:

const spy = jest.spyOn(Viewer, 'default').mockImplementation(() => jest.fn());

But now I want to spy on Viewer.

EDIT:

This was my final solution. @brian-lives-outdoors answer is correct, but I didn't describe my problem accurately. The third party library I was trying to mock was slightly more plex because it export a module containing several constructors. It looked like this:

module.exports = {
  Viewer: require('./path/Viewer'),
  Foo: require('./foo_path/Foo'),
  Bar: require('./bar_path/Bar')
}

Then inside ./path/Viewer is was as I previously described above.

Here's what my solution ended up looking like:

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 What I don't understand is if I ment out the line jest.mock('lib'); above, it doesn't work...Why?

Why isn't genMockFromModule sufficient by itself?

I am having trouble mocking a third party dependency. I'm always recieving this error:

Cannot spy the undefined property because it is not a function; undefined given instead

Here are the details of this issue. First, this is the function I am testing:

File: 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.',
            );
        }
    },
}

Looking at the source code for my third-party, Viewer is very simple and looks like this:


function Viewer(){
 // Doing things
}

Viewer.prototype.foo = function(){

}

module.exports = Viewer;

And finally, here is my test.

File: /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();
        });
    });
});

How can I mock & spy on Viewer itself?

I've done this in the past:

const spy = jest.spyOn(Viewer.prototype, 'foo').mockImplementation(() => jest.fn());

I've also tried default with no luck:

const spy = jest.spyOn(Viewer, 'default').mockImplementation(() => jest.fn());

But now I want to spy on Viewer.

EDIT:

This was my final solution. @brian-lives-outdoors answer is correct, but I didn't describe my problem accurately. The third party library I was trying to mock was slightly more plex because it export a module containing several constructors. It looked like this:

module.exports = {
  Viewer: require('./path/Viewer'),
  Foo: require('./foo_path/Foo'),
  Bar: require('./bar_path/Bar')
}

Then inside ./path/Viewer is was as I previously described above.

Here's what my solution ended up looking like:

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 What I don't understand is if I ment out the line jest.mock('lib'); above, it doesn't work...Why?

Why isn't genMockFromModule sufficient by itself?

Share Improve this question edited Sep 16, 2019 at 14:20 calbear47 asked Aug 29, 2019 at 14:48 calbear47calbear47 1,2113 gold badges21 silver badges42 bronze badges 1
  • @brian-lives-outdoors Any thoughts on this question? You seem to be an expert on mocking dependencies – calbear47 Commented Aug 29, 2019 at 15:02
Add a ment  | 

2 Answers 2

Reset to default 5

The example code mixes ES6 import/ export syntax with Node module.exports syntax...

...but based on a library that looks like this:

lib.js

function Viewer() { }

Viewer.prototype.foo = function () { }

module.exports = Viewer;

...it would be used like this:

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);

...and to spy on Viewer you would need to mock the entire library in your test:

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!
});

Note that if the library was an ES6 library then you could spy on the default export directly like this:

import * as lib from './lib';

const spy = jest.spyOn(lib, 'default');  // <= spy on the default export

...but because of the way Babel handles the interop between ES6 and non-ES6 code this approach doesn't work if the library is not ES6.


Edit: response to the follow-up question

jest.genMockFromModule generates a mocked version of the module and returns it.

So for example this:

const mock = jest.genMockFromModule('lib');

...generates a mocked version of lib and assigns it to mock. Note that this does not mean that the mock will be returned when lib is required during a test.

jest.genMockFromModule can be useful when creating a manual mock:

__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

In your final solution you have these two lines:

jest.genMockFromModule('lib');
jest.mock('lib');

This line:

jest.genMockFromModule('lib');

...doesn't actually do anything since it is generating a mock of the module but the returned mock isn't being used for anything.

This line:

jest.mock('lib');

...tells Jest to auto-mock the lib module and is the only line that is needed in this case.

Here is a solution:

util.js

const util = {
  isElement() {}
};

module.exports = util;

View.js, the third-party module:

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.');
    }
  }
};

Unit test:

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();
    });
  });
});

Unit test result:

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

本文标签: javascriptHow do I spyOn third party function with jestStack Overflow