Mock function inside function with Jest - javascript

I would like to mock a function that is called inside another function and also control what it can return or possibly throw an error.
main.ts
import function from './function';
const main = async() => {
...
try {
value = await function(arguments);
} catch(error) {
...
}
}
For this dumb case for example if I wanted to test that "function" has been called with the right parameters, mocking its return value. I also want to be able to mock a thrown error from that function.
Basically I dont want my test to go inside this function, I just want to mock it and test the rest of the main function with the return value, and test the catch handler.
Here is the base of what I would want to do:
main.test.ts
import main from './main';
import function from './function';
describe('Test main', () => {
it('should call function with the right arguments', async() => {
function = jest.fn(() => value);
await main();
expect(function).toHaveBeenLastCalledWith(arguments);
// expect not to have thrown error
});
it('should handle the error of function', async() => {
function = jest.fn().mockRejectedValue(new Error('error'));
await main();
expect(function).toHaveBeenLastCalledWith(arguments);
// expect to have thrown error
});
});
Thank you for your help!

You need to mock the module.
Something like this:
import function from './function';
jest.mock("./function")
it("some", () => {
function.mockRejectedValue(new Error('error'));
//do stuff
expect(function).toHaveBeenLastCalledWith(arguments);
});

Related

Mock implementation of another module not working in jest

I am unit testing (using jest) a function named "getTripDetails" (inside file trip.js) that calls another file "getTrace.js" from different module (which exports a function as shown below).
I want to mock the call of function "getTrace" while testing "getTripDetails" function.
file: trips.js
const gpsTrace = require("./gpsTrace");
getTripDetails = async(req, res)=>{
let gpsTraceRes = await gpsTrace(req.body, req.adToken)
//more code...
return {status:200};
}
file: getTrace.js
module.exports = async(payload, token) =>{
try {
//code
} catch (e) {
error(e)
throw new Error(e)
}
}
This is what i tried after reading the docs.
file: test.js
let ctrl = require("./trips");
describe("API -- testing", function () {
it("Trip details", async function () {
jest.mock('./gpsTrace');
const gpsTrace = require('./gpsTrace');
gpsTrace.mockImplementation(() => {});
gpsTrace();
await ctrl.getTripDetails({},{});
expect(response.status).to.eql(200);
});
});
It did not get mocked, instead it was calling the original implementation.
Any suggesstions?
You were pretty close! Here are the updated files with comments describing the changes:
gpsTrace.js
Added a console.log message. We won't see this in the test if the mock works successfully.
module.exports = async (payload, token) => {
try {
//code
console.log("You won't see me in the Jest test because of the mock implementation")
} catch (e) {
error(e)
throw new Error(e)
}
}
trips.js
You needed to export your code to be used in other modules. Seeing as you're calling ctrl.getTripDetails() in the test, it makes sense to export your getTripDetails() on an object at the bottom of the file.
const gpsTrace = require("./gpsTrace");
const getTripDetails = async (req, res) =>{
let gpsTraceRes = await gpsTrace(req.body, req.adToken)
//more code...
return { status:200 };
}
module.exports = {
getTripDetails,
}
gpsTrace.test.js
Make sure to import your modules at the top of the file. Remember that ctrl.getTripDetails({}, {}) calls gpsTrace internally, so no need to call it twice in your test. You also needed to save the response returned from getTripDetails into a variable to be able to compare it: const response = await ctrl.getTripDetails({}, {});.
// make sure your require statements go at the top of the module
const gpsTrace = require('./gpsTrace');
let ctrl = require("./trips");
jest.mock('./gpsTrace');
gpsTrace.mockImplementation(() => {});
describe("API -- testing", function () {
it("Trip details", async function () {
// ctrl.getTripDeals() calls your gpsTrace function internally, so no need to call it twice
// gpsTrace(); <-- can be removed
// you needed to save the returned response into a variable to be able to test it.
const response = await ctrl.getTripDetails({}, {});
expect(response.status).toEqual(200);
});
});
Result
After running the test it now successfully passes. Notice that we DO NOT see the console.log message in the gpsTrace function, which indicates our mockedImplementation of the function is working in the test script. 👍

How to mock const method in jest?

I unit test code in typescript, use jest. Please teach me how to mock getData to return the expected value. My code as below:
// File util.ts
export const getData = async () => {
// Todo something
return data;
}
// File execution.ts import { getData } from './util';
function execute()
{
// todo something
const data = await getData();
// todo something
}
The problem is that your function returns a promise. Depends on how you use it there are several ways to mock it.
The simplest way would be to mock it directly, but then it will always return the same value:
// note, the path is relative to your test file
jest.mock('./util', () => ({ getData: () => 'someValue' }));
If you want to test both the resolved and the rejected case you need to mock getData so it will return a spy where you later on can change the implementation use mockImplementation. You also need to use async/await to make the test work, have a look at the docs about asynchronous testing:
import { getData } from './util';
jest.mock('./util', () => ({ getData: ()=> jest.fn() }));
it('success case', async () => {
const result = Promise.resolve('someValue');
getData.mockImplementation(() => result);
// call your function to test
await result; // you need to use await to make jest aware of the promise
});
it('error case', async () => {
const result = Promise.reject(new Error('someError'));
getData.mockImplementation(() => result);
// call your function to test
await expect(result).rejects.toThrow('someError');
});
Try the following in your test file.
Import the function from the module.
import { getData } from './util';
Then mock the module with the function and its return value after all the import statements
jest.mock('./util', () => ({ getData: jest.fn() }))
getData.mockReturnValue("abc");
Then use it in your tests.
Because mocking expression functions can be a real pain to get right, I'm posting a full example below.
Scenario
Let's say we want to test some code that performs some REST call, but we don't want the actual REST call to be made:
// doWithApi.ts
export const doSomethingWithRest = () => {
post("some-url", 123);
}
Where the post is a function expression in a separate file:
// apiHelpers.ts
export const post = (url: string, num: number) => {
throw Error("I'm a REST call that should not run during unit tests!");
}
Setup
Since the post function is used directly (and not passed in as a parameter), we must create a mock file that Jest can use during tests as a replacement for the real post function:
// __mocks__/apiHelpers.ts
export const post = jest.fn();
Spy and Test
Now, finally inside the actual test, we may do the following:
// mockAndSpyInternals.test.ts
import {doSomethingWithRest} from "./doWithApi";
afterEach(jest.clearAllMocks); // Resets the spy between tests
jest.mock("./apiHelpers"); // Replaces runtime functions inside 'apiHelpers' with those found inside __mocks__. Path is relative to current file. Note that we reference the file we want to replace, not the mock we replace it with.
test("When doSomethingWithRest is called, a REST call is performed.", () => {
// If we want to spy on the post method to perform assertions we must add the following lines.
// If no spy is wanted, these lines can be omitted.
const apiHelpers = require("./apiHelpers");
const postSpy = jest.spyOn(apiHelpers, "post");
// Alter the spy if desired (e.g by mocking a resolved promise)
// postSpy.mockImplementation(() => Promise.resolve({..some object}))
doSomethingWithRest();
expect(postSpy).toBeCalledTimes(1)
expect(postSpy).toHaveBeenCalledWith("some-url", 123);
});
Examples are made using Jest 24.9.0 and Typescript 3.7.4

How to unit test promise rejection in React and Jest

I am trying to write a unit test for a react component. It's a fairly standard component which calls a promise-returning method and uses 'then' and 'catch' to handle the resolution. My test is trying to validate that it calls the correct method when the promise is rejected however despite following what i believe is a standard patttern, I cannot get jest to validate the call. I have listed the relevant files here and have also put up a github sample, which is linked at the bottom of the question. That sample is simply a new react app created using npx and the files below added in.
Here is my example component:
import React from 'react';
import api from '../api/ListApi';
class ListComponent extends React.Component {
constructor(props) {
super(props);
this.fetchListSuccess = this.fetchListSuccess.bind(this);
this.fetchListFailed = this.fetchListFailed.bind(this);
}
fetchList() {
api.getList()
.then(this.fetchListSuccess)
.catch(this.fetchListFailed);
}
fetchListSuccess(response) {
console.log({response});
};
fetchListFailed(error) {
console.log({error});
};
render() {
return(<div>Some content</div>);
};
}
export default ListComponent;
Here is the api class (note, the api doesnt exist if you run the app, its just here for example):
const getList = () => fetch("http://someApiWhichDoesNotExist/GetList");
export default { getList };
And here is the test case:
import ListComponent from './ListComponent';
import api from '../api//ListApi';
describe('ListComponent > fetchList() > When the call to getList fails', () => {
it('Should call fetchListFailed with the error', async () => {
expect.hasAssertions();
//Arrange
const error = { message: "some error" };
const errorResponse = () => Promise.reject(error);
const componentInstance = new ListComponent();
api.getList = jest.fn(() => errorResponse());
componentInstance.fetchListFailed = jest.fn(() => { });
//Act
componentInstance.fetchList();
//Assert
try {
await errorResponse;
} catch (er) {
expect(componentInstance.fetchListFailed).toHaveBeenCalledWith(error);
}
});
});
The problem is that the test is not executing the catch block, so, in this case, the expect.hasAssertions() is failing the test. Can anyone help me understand the catch block is not executing? Wrapping the await in the try block and asserting in the catch seems to be a standard pattern in the docs but I am fairly new to Js and React and am obviously doing something wrong.
Here is the sample project on GitHub. Any help would be greatly appreciated =)
In your console:
const errorResponse = () => Promise.reject();
await errorResponse;
//() => Promise.reject()
You're awaiting a function, not the result of the call to that function. You want to:
await errorResponse();
EDIT:
In addition to that, the rest of your test is confusing. I believe you actually want to test what happens when the fetchList method of your component is called, and it fails, I assume. So you need to call it in your test, and await it's response:
Update your component's fetchList method to return the promise.
await componentInstance.fetchList() instead of await errorResponse()
Because you catch the error in fetchList you'll never enter the catch or the try...catch so your final test should look like this:
Test:
//Arrange
const error = { message: "some error" };
const errorResponse = () => Promise.reject(error);
const componentInstance = new ListComponent();
api.getList = jest.fn(() => errorResponse());
componentInstance.fetchListFailed = jest.fn(() => { });
//Act
await componentInstance.fetchList();
expect(componentInstance.fetchListFailed).toHaveBeenCalledWith(error);
Component:
fetchList() {
return api.getList()
.then(this.fetchListSuccess)
.catch(this.fetchListFailed);
}
My two cents, with my own case in a React native app:
In my component I have:
const handleRedirect = React.useCallback(() => {
client.clearStore()
.then(navigation.push('SomeScreen'))
.catch(e => logger.error('error', e));
}, []);
My test is this one, where I want to test if the promise is rejected:
it('should throw an exception on clearStore rejected', async () => {
const client = {
clearStore: jest.fn()
};
const error = new Error('clearStore failed');
client.clearStore.mockReturnValue(Promise.reject(error));
expect.assertions(1);
await expect(client.clearStore())
.rejects.toEqual(Error('clearStore failed'));
});

sinon stub not restoring properly if the stubbing method is deconstructed

Given the test codes below:
subAuthCall:
const sinon = require('sinon')
const sandbox = sinon.createSandbox()
function stubSuccessCall() {
return sandbox.stub(authorization, 'authorize').returns({enabled: true});
}
function stubFailedCall() {
return sandbox.stub(authorization, 'authorize').returns({enabled: false});
function restoreSub() {
sandbox.restore();
}
in the authorization.js file, I have:
function authorize(data) {
return data.enabled;
}
module.exports = {
authorize
}
and then in the middleware, I have:
const {authorize} = require('./authorization')
async function check() {
//import auth data
console.log(authorize(data))
if (authorize(data)) {
//resolve to true
} else {
//reject
}
}
Then in the test case, I called:
afterEach(authorizationStub.restorStub);
describe('auth testing', () => {
it('test successful', () => {
authorizationStub.stubSuccessCall();
return check().then(res => {expect(res.result).to.equl(true)});
})
it('test failed', () => {
authorizationStub.stubFailedCall();
return check().then(res => {expect(res.result).to.equl(false)});
})
})
It's a overly simplified auth logic and test case. The weird problem I had is that if I run both test cases, it prints out:
1 - test successful
true // from console.log(authorize(data))
2 - test failed
true // from console.log(authorize(data))
The 1st test case passed and the 2nd one failed (because the stubbing didn't return the right result)
but in the test failed, case, it supposed to return false as how I stub it in stubFailedCall, but it still has the same result in stubSuccessCall. I have verified the restoreStub is called.
I accidentally came across the fix: In the middleware, I used to have:
const {authorize} = require('./authorization')
...
but if I changed this to:
const auth = require('./authorization')
and then in the codes, instead of:
authorize(data)
I do:
auth.authorize(data)
This will work - both test cases will pass without any other changes.
My question is, why sinon stub/restoring stub didn't work if I deconstruct the authorize call in the middleware, but if I use an object to make the call, it will work? What's the mechanism behind this?

How to expect one function to call another function?

I am trying to mock a function call, and expect it to have called another function within it once.
myFunctions.test.js
import { resetModal } from '../myFunctions.js';
describe('resetModal', () => {
it('calls the clearSomethingInModal function', () => {
const clearSomethingInModal = jest.fn();
resetModal();
expect(clearSomethingInModal.mock.calls.length).toBe(1);
})
})
myFunctions.js
export resetModal() {
clearSomethingInModal()
}
However, Jest output says that it has not been called. How can I best do this?
Your approach does not work because you mock clearSomethingInModal only in the context of your test file, so clearSomethingInModal in the myFunctions.js is still the original. The main point is that you can't mock something that is directly created in myFunctions.js. The only thing that you can mock are:
Modules that you import to myFunctions.js, like import clearSomethingInModal from 'clearSomethingInModal';
Callbacks that you pass into your function when calling them from your test;
This makes sense if you think about myFunctions.js as a blackbox, where you can control what goes in, like imports or function arguments, and where you can test what comes out. But you can not test the stuff that happens inside the box.
Here are two example that reflect the 2 points in the list:
myFunctions.test.js
import { resetModal } from '../myFunctions.js';
import clearSomethingInModal from 'clearSomethingInModal';
jest.mock('clearSomethingInModal', () => jest.fn())
describe('resetModal', () => {
it('calls the clearSomethingInModal function', () => {
resetCreationModal();
expect(clearSomethingInModal.mock.calls.length).toBe(1);
})
})
myFunctions.js
import clearSomethingInModal from 'clearSomethingInModal';
export resetModal() {
clearSomethingInModal()
}
myFunctions.test.js
import { resetModal } from '../myFunctions.js';
describe('resetModal', () => {
it('calls the clearSomethingInModal function', () => {
const clearSomethingInModal = jest.fn();
resetCreationModal(clearSomethingInModal);
expect(clearSomethingInModal.mock.calls.length).toBe(1);
})
})
myFunctions.js
export resetModal(clearSomethingInModal) {
clearSomethingInModal()
}
Another way is to use done and mock or spy on the implementation of the last function and check if the previous function was called by then.
it('should call function2 after function1', (done) => {
expect.assertions(2)
function2.mockImplementationOnce(() => {
expect(function1).toHaveBeenCalled()
done()
})
act() // This is where you run function you are testing
})
The drawback of this solution is that the structure of the test is not
// arrange
// act
// assert
but rather
// arrange & assert
// act

Categories