How do I spyOn third party function with jest? - javascript

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 complex 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 comment out the line jest.mock('lib'); above, it doesn't work...Why?
Why isn't genMockFromModule sufficient by itself?

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

Related

How to preload a javascript dependency via Jest?

Is it possible to inject the dependency via jest or alternative without having to import 'lib' into the file itself?
Example file.js
// Note: lib is not defined in this file
export const getDep = (str) => {
lib('something');
return str;
}
Example jest.config.js
module.exports = {
globals: {
window: {
lib: () => {} // does not work
},
lib: () => {} // does not work
},
setupFiles: ['<rootDir>/jest/setupFiles.js'], // doesnt work either
// ...other stuff
}
Example jest/setupFiles.js
const lib = () => {};
console.log(lib);
Example file.test.js
import { getDep } from "file.js";
describe('Test injection of global dependency using jest.config', () => {
it("should not break test", () => {
const result = getDep('hello');
expect(result).toEqual('hello')
})
})
Also tried jest - setupFiles
Error ReferenceError: lib is not defined
1 |
2 | export const getDep = (str) => {
> 3 | lib('something');
| ^
4 | return str;
Ok thanks to #jonrsharpe for a partial successful solution.
I had to use jest setupFiles, but also I had to add the dependency to the window object.
window.lib = lib;
const lib = () => {};
window.lib = lib; // Added code here..
console.log(lib);

How to mock nanoid for testing?

I'm trying to mock nanoid for my testing but it doesn't seem to be working.
my function
public async createApp(appDto: ApplicationDto): Promise<string> {
const appWithToken = { ...appDto, accessToken: nanoid() };
const application = await this.applicationModel.create(appWithToken);
return application.id;
}
My test:
beforeEach(() => {
mockRepository.create.mockResolvedValueOnce({ id: mockId });
});
test("creates application and returns an id", async () => {
const mockAppDto: ApplicationDto = { email: "123#mock.com" };
const application = await applicationService.createApplication(mockAppDto);
expect(mockRepository.create).toHaveBeenCalledWith(mockAppDto); //how do I mock the nanoid here?
expect(application).toBe(mockId);
});
So basically I'm struggling to figure out how to mock the nanoid which is generated inside the function.
I've tried the following at the top of the file:
jest.mock('nanoid', () => 'mock id');
however it doesn't work at all.
Any help would be appreciated!
You didn't mock the nanoid module correctly. It uses named exports to export the nanoid function.
Use jest.mock(moduleName, factory, options) is correct, the factory argument is optional. It will create a mocked nanoid function.
Besides, you can use the mocked function from ts-jest/utils to handle the TS type.
E.g.
Example.ts:
import { nanoid } from 'nanoid';
export interface ApplicationDto {}
export class Example {
constructor(private applicationModel) {}
public async createApp(appDto: ApplicationDto): Promise<string> {
const appWithToken = { ...appDto, accessToken: nanoid() };
const application = await this.applicationModel.create(appWithToken);
return application.id;
}
}
Example.test.ts:
import { nanoid } from 'nanoid';
import { Example, ApplicationDto } from './Example';
import { mocked } from 'ts-jest/utils';
jest.mock('nanoid');
const mnanoid = mocked(nanoid);
describe('67898249', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should pass', async () => {
mnanoid.mockReturnValueOnce('mock id');
const mockAppDto: ApplicationDto = { email: '123#mock.com' };
const mockApplicationModel = { create: jest.fn().mockReturnValueOnce({ id: 1 }) };
const example = new Example(mockApplicationModel);
const actual = await example.createApp(mockAppDto);
expect(actual).toEqual(1);
expect(mockApplicationModel.create).toBeCalledWith({ email: '123#mock.com', accessToken: 'mock id' });
});
});
test result:
PASS examples/67898249/Example.test.ts (9.134 s)
67898249
✓ should pass (4 ms)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
Example.ts | 100 | 100 | 100 | 100 |
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.1 s

Mocking function object with jest

I currently have an object that is used to interact with my API api.js:
export var Auth = (function () {
var Login = async function(username, password) {
//Async login code for interacting with the API
};
return {
Login: Login
}
});
And this object is imported inside another file, login.js:
import * as API from './api';
export var LoginRequestHandler = function() {
//processess user input, then calls:
try {
await API.Auth().Login(username, password);
} catch(e) {
throw new Error(e);
}
This is my jest test:
import * as API from '../api';
import * as User from '../user';
jest.mock('../api');
const spy = jest.spyOn(API.Auth(), 'Login');
User.LoginRequestHandler().then(() => {
expect(spy).toHaveBeenLastCalledWith('theUsername', 'thePassword');
}).catch(error => console.log(error));
This is my mock file, __mock__/api.js:
export var Auth = (function () {
var Login = async function(username, password) {
return Promise.resolve(true);
};
return {
Login: Login
}
});
I retrieve theUsername and thePassword through document.getElementId() in the LoginRequestHandler and create my own DOM for the test above.
Adding console.log(username) in the LoginRequestHandler reveals that it is being called and is able to get the right values. Furthermore, adding a console.log(username) in API.Auth().Login also reveals that it is getting the right values as well. However, when I look at my test logs, I see: Number of calls: 0 for the mock function and the test results in errors.
I assume that I am trying to spy on the wrong function, and is there anyway that I can fix this?
Every time you call API.Auth(), it will return a new object which has a Login method. So, the object created in LoginRequestHandler function and created by jest.spyOn(API.Auth(), 'Login') statement in your test case are different. The spy is only added to the later one. The Login method in LoginRequestHandler function is not spied.
So, here I am going to use jest.mock() to mock the api.js module without putting mocked object to __mocks__ directory. E.g.
api.js:
export var Auth = function () {
var Login = async function (username, password) {};
return {
Login: Login,
};
};
user.js:
import * as API from './api';
export var LoginRequestHandler = async function () {
const username = 'theUsername';
const password = 'thePassword';
try {
await API.Auth().Login(username, password);
} catch (e) {
throw new Error(e);
}
};
user.test.js:
import * as API from './api';
import * as User from './user';
jest.mock('./api', () => {
const auth = { Login: jest.fn() };
return {
Auth: jest.fn(() => auth),
};
});
describe('61643983', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should login', () => {
expect.assertions(2);
return User.LoginRequestHandler().then(() => {
expect(API.Auth).toBeCalledTimes(1);
expect(API.Auth().Login).toHaveBeenLastCalledWith('theUsername', 'thePassword');
});
});
it('should throw error', () => {
expect.assertions(4);
const mError = new Error('user not found');
API.Auth().Login.mockRejectedValueOnce(mError);
return User.LoginRequestHandler().catch((e) => {
expect(API.Auth).toBeCalled();
expect(API.Auth().Login).toHaveBeenLastCalledWith('theUsername', 'thePassword');
expect(e).toBeInstanceOf(Error);
expect(e.message).toMatch(/user not found/);
});
});
});
unit test results with 100% coverage:
PASS stackoverflow/61643983/user.test.js (11.507s)
61643983
✓ should login (5ms)
✓ should throw error (3ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
user.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 13.023s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61643983

Using Jest.fn() to see if s3.upload function was called...what am I doing wrong?

I am a bit new to testing and I have been stuck on this issue for quite some time. So I am trying to test a s3.upload() function to see if it called, not to see if it actually uploads the object. The only constraint is that I cannot use any npm packages to mock out the functionality of the s3 bucket.
I was trying to follow this tutorial (How to mock a function inside another function (which I am testing) using sinon?) that uses sinon as a stub, but instead use jest instead. Any help or guidance with issue is appreciated.
// function.js
const uploadToS3 = (params) => {
const response = s3.upload(params).promise();
return response;
}
// functions.test.js
describe("Lambda Handler Function", () => {
test('To test to see if the uploadToS3 function was called', () => {
const sampleParam = {
Bucket: 'BucketName',
Key: 'BucketKey.zip',
Body: 'SGVsbG8sIFdvcmxk'
}
expect(uploadToS3(sampleParam).response).toBeCalled()
})
})
You can use jest.mock(moduleName, factory, options) to mock aws-sdk.
E.g.
function.js:
import AWS from 'aws-sdk';
const s3 = new AWS.S3();
const uploadToS3 = async (params) => {
const response = await s3.upload(params).promise();
return response;
};
export { uploadToS3 };
function.test.js:
import { uploadToS3 } from './function';
import AWSMock from 'aws-sdk';
jest.mock('aws-sdk', () => {
const mS3 = { upload: jest.fn().mockReturnThis(), promise: jest.fn() };
return { S3: jest.fn(() => mS3) };
});
describe('60970919', () => {
it('should pass', async () => {
const mS3 = new AWSMock.S3();
const mResponse = { Bucket: 'xxx' };
mS3.upload({}).promise.mockResolvedValueOnce(mResponse);
const actual = await uploadToS3({});
expect(actual).toEqual(mResponse);
expect(mS3.upload).toBeCalledWith({});
expect(mS3.upload().promise).toBeCalled();
});
});
unit test results with 100% coverage:
PASS stackoverflow/60970919/function.test.js (13.818s)
60970919
✓ should pass (9ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
function.js | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 15.486s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60970919

Mocking a node module in a jest test

I have a Jest test that I am writing for a function which makes an API call. If the API call returns a 403, a function from a node module should be called. I am trying to test this using a mock jest function, but I cannot get the test to use the mocked version of the module I am making.
file.spec.js
import file from './file'
const mockedNodeModule = jest.genMockFromModule('nodeModule')
mockedNodeModule.namedExport = { logout: jest.fn() }
it('call returns a 403', async () => {
await file.apiCall('brand', 'entityType', 'name')
expect(mockedNodeModule.namedExport.logout).toHaveBeenCalled()
})
file.js
import { namedExport } from './nodeModule';
import api from './api';
const apiCall = () => {
return api.makeCall().then(
() => {},
(error) => {
if (error.status === 403) {
namedExport.logout();
}
},
);
};
export default { apiCall };
The test always fails when I check whether mockedNodeModule.namedExport.logout has been called. When I put a breakpoint on the line that it is called, it seems that the mocked version is not being used when the test is running (i.e. it is still using the module from my node_modules). I have also tried using jest.mock() as well, but the result is the same. Is there something wrong in the way that I am setting up the test? Can jest not mock node modules in cases like this?
jest.mock(moduleName, factory, options) should work.
E.g.
file.js:
import { namedExport } from './nodeModule';
import api from './api';
const apiCall = () => {
return api.makeCall().then(
() => {},
(error) => {
if (error.status === 403) {
namedExport.logout();
}
},
);
};
export default { apiCall };
api.js:
function makeCall() {}
export default { makeCall };
nodeModule.js:
export const namedExport = {
logout() {
console.log('real implementation');
},
};
file.test.js:
import file from './file';
import api from './api';
import { namedExport } from './nodeModule';
jest.mock('./nodeModule', () => {
const mNamedExport = {
logout: jest.fn(),
};
return { namedExport: mNamedExport };
});
jest.mock('./api', () => {
return { makeCall: jest.fn() };
});
describe('59831697', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should handle error if http status equal 403', async () => {
const mError = { status: 403 };
api.makeCall.mockRejectedValueOnce(mError);
await file.apiCall();
expect(namedExport.logout).toHaveBeenCalledTimes(1);
});
});
Unit test results with coverage report:
PASS src/stackoverflow/59831697/file.test.js (13.506s)
59831697
✓ should handle error if http status equal 403 (7ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 50 | 66.67 | 100 | |
file.js | 100 | 50 | 66.67 | 100 | 8 |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 15.448s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59831697

Categories

Resources