Jest is unable to detect the mock function call - javascript

I'm writing the unit tests for one on the controller in the Express app. I'm mocking res.send method and expect it to be called when user passes the valid data like this:
describe('>> CONTROLLERS -- Exercise -- deleteExercise', () => {
let res
beforeEach(() => {
res = {
send : jest.fn()
}
});
it('sends error as response if id was not passed', () => {
const req = {
body : {}
}
deleteExercise(req, res)
expect(res.send).toHaveBeenCalledWith({
error : 'please pass id field to delete the exercise.'
})
})
it('calls the deleteExercise class method if the id was passed', () => {
const req = {
body : {
id : 1234
}
}
deleteExercise(req, res)
expect(Exercise.deleteExercise).toHaveBeenCalledWith(1234)
expect(res.send).toHaveBeenCalled()
// expect(res.send).toHaveBeenCalledWith(mockData)
})
})
The first test runs fine, but the second is not. I've added consoles to check if the res.send was called or not, in code it's being called, but Jest fails for expect(res.send).toHaveBeenCalled() test. Could you please help me with what I'm missing here...

After taking some time into debugging, I think you also need to provide status to your mock response.
mockResponse = {
send : jest.fn()
status: jest.fn(() => mockResponse),
}
describe('>> CONTROLLERS -- Exercise -- deleteExercise', () => {
let mockResponse
beforeEach(() => {
mockResponse = {
send : jest.fn()
status: jest.fn(() => mockResponse),
}
});
afterEach(() => {
jest.clearAllMocks();
});
it('sends error as response if id was not passed', () => {
const mockRequest = {
body : {}
}
deleteExercise(mockRequest, mockResponse)
expect(mockResponse.send).toHaveBeenCalledWith({
error : 'please pass id field to delete the exercise.'
})
})
it('calls the deleteExercise class method if the id was passed', () => {
const mockRequest = {
body : {
id : 1234
}
}
deleteExercise(mockRequest, mockResponse)
// deleteExercise is called with req, res arguments
expect(Exercise.deleteExercise).toHaveBeenCalled()
expect(Exercise.deleteExercise).toHaveBeenCalledWith(mockRequest, mockResponse)
expect(Exercise.deleteExercise).toHaveBeenCalledTimes(1)
expect(mockResponse.send).toHaveBeenCalled()
expect(mockResponse.send).toHaveBeenCalledWith(1234)
expect(mockResponse.send).toHaveBeenCalledTimes(1)
})
})
deleteExercise function is called with res and req, not with the id.
First, check deleteExercise is called or not, then check whether the same function is called with arguments. Then check later how many times the function is called.
One more thing, clear all the mocks after each test case.
afterEach(() => {
jest.clearAllMocks();
});

Related

How to Inject multiple mock promises to unit test the fetch in JS?

I'm trying to setup unit tests for the api calls in my JS project.
Able to set it up for a single API call, using the following format
describe('Token refresh success', () => {
beforeAll(() => {
global.fetch = () =>
Promise.resolve({
json: () => Promise.resolve(mockTokenCreateSuccess),
})
})
afterAll(() => {
global.fetch = unmockedFetch
})
test('testTokenRefreshSuccess', async () => {
const tokenData = await c.tokenRefresh();
expect(tokenData.access_token).toEqual('SYoHdm4yw');
expect(tokenData.refresh_token).toEqual('QxJ3yEgX4NThbTE66u7lshWTpQkRBilq');
});
})
Now this format works great, and I can create individual tests by injecting one promise.
Now, there is a case I want to unit test where a particular API call is made twice. I need to inject fail response the first time, and success response the second time.
I tried the following approach, but did not work:
describe('Token refresh trigger as expected on create token fail', () => {
beforeAll(() => {
global.fetch = () => [
Promise.resolve({
json: () => Promise.reject(mockError(400)),
}),
Promise.resolve({
json: () => Promise.resolve(mockTokenCreateSuccess),
})
]
})
afterAll(() => {
global.fetch = unmockedFetch
})
test('testTokenRefreshTriggerOnTokenCreateFail', async () => {
const tokenData = await c.tokenRefresh();
expect(tokenData.access_token).toEqual('jkhjk');
expect(tokenData.refresh_token).toEqual('dfdfdf');
});
})
and my tokenRefresh() function is supposed to get called 2 times if it gets 400 error.
async tokenRefresh(retryCount = 0) {
console.log('tokenRefresh');
const request = this.getTokenRefreshRequest();
try {
const response = await this.fetchData(request);
console.log('token refresh success', response);
return { access_token, refresh_token };
} catch (err) {
if ((err.status == 400 || err.status == 401) && retryCount < 2) {
await this.tokenRefresh(retryCount++);
} else {
throw new Error(`unable to refresh a token from API ${err.status}`);
}
}
};
I'm able to verify that by using single promise
Promise.resolve({
json: () => Promise.reject(mockError(400)),
})
the tokenRefresh() gets called again as expected, but in the second time I could not figure out how to pass the success promise, at the moment it fails the second time too.
I was able to find a working combination
beforeAll(() => {
global.fetch = fetchMock.mockRejectOnce(customError) // makes sure first api call fails
.mockResponseOnce(JSON.stringify(successResponse)) // makes sure second api call succeeds
})
This made my test pass

Unit Test: Stub/rewire a function inside a server request

I want to test a route that makes external api calls.
I would like to stub the functionThatShouldBeStubbed so I can skip the external api call and focus on testing the route instead.
I am using Sinon and rewire, because if I understood correctly I cannot stub a function that was exported the way it currently is.
However, it seems like even though rewire replaced the function, my test is still making external api call. It seems like sinon is not aware that the function was rewired. How can I make this situation work?
//--------------------------
//../target.js
const functionThatShouldBeStubbed = async () => {
const results = await external_API_call();
return results;
}
module.exports = {
functionThatShouldBeStubbed,
/*more other functions*/
}
//--------------------------
//../index.js
app.use(require('endpoint.js'));
//--------------------------
//endpoint.js
const { functionThatShouldBeStubbed } = require("target.js");
router.post('endpoint', async(req, res) => {
//do lots of stuff
const results = await functionThatShouldBeStubbed();
if(results.error) { return res.status(207).send({ /*stuff */})}
//...more stuff
})
//--------------------------
//test.js
const server = require("../index.js");
const rewire = require('rewire')
const restoreTarget = rewire('../target.js');
describe("Should return appropriate error code to requester", function () {
it("Should return 207 in this case", function (done) {
const targetStub = sinon.stub().resolves({msg: 'fake results', statusCode: 207})
const targetRewired = restoreTarget.__set__("functionThatShouldBeStubbed", targetStub);
chai.request(server)
.post("/endpoint")
.send('stuff over')
.catch((error) => {
console.log("Error: ", error)
done();
})
.then((res) => {
expect(targetStub.callCount).to.equal(1);
res.should.have.status(207);
restoreTarget();
targetStub.restore();
done();
})
})
})
Many thanks!
Edit: updated code for more detail
Edit2: updated code again to show import method
You shouldn't need rewire at all here based on how your module is being exported. The following should work
//test.js
const target = require ("../target");
const server = require("../index");
describe("Should return appropriate error code to requester", () => {
it("Should return 207 in this case", done => {
const targetStub = sinon
.stub(target, "functionThatShouldBeStubbed")
.resolves({msg: 'fake results', statusCode: 207})
chai.request(server)
.post("/endpoint")
.send('stuff over')
.then(res => {
expect(targetStub.callCount).to.equal(1);
res.should.have.status(207);
targetStub.restore();
done();
})
})
})

Test callback function with jest

I'm trying to test a function with a callback inside. I set up a mock function, but I also need to test a callback.
I've tried to separate it as another mock function, but it doesn't counted as covered.
Function I'm trying to test:
export const checkDescription = async page => {
const metaDescription = await page.$eval(
'meta[name="description"]',
description => description.getAttribute("content")
);
return metaDescription;
};
I've mocked the page function :
const page = {
$eval: jest.fn(() => "Value")
};
my test :
test("Should return description", async () => {
expect(await checkDescription(page)).toBe("Value");
expect(page.$eval).toHaveBeenCalled();
});
I've tried to separate description :
const description = {
getAttribute: jest.fn(() => "Value")
};
but I don't think that it's a correct way to cover description inside $eval.
You're close!
The description arrow function is passed to your page.$eval mock function so you can use mockFn.mock.calls to retrieve it.
Once you've retrieved it, you can call it directly to test it and get full code coverage:
test("Should return description", async () => {
expect(await checkDescription(page)).toBe("Value"); // Success!
expect(page.$eval).toHaveBeenCalled(); // Success!
const description = page.$eval.mock.calls[0][1]; // <= get the description arrow function
const getAttributeMock = jest.fn(() => 'mock content');
expect(description({ getAttribute: getAttributeMock })).toBe('mock content'); // Success!
expect(getAttributeMock).toHaveBeenCalledWith('content'); // Success!
// Success! checkDescription now has full code coverage
});
I receive async messages from serial port via callbacks. Try to read here:
https://jest-bot.github.io/jest/docs/asynchronous.html
import { InpassTerminal } from "../src/main.js"
jest.setTimeout(45000);
describe('Basic tests', () => {
test('1. Host connection', async (done) => {
await new Promise( resolve => setTimeout(resolve, 500) );
const commandTest = {actionCode: '12345', terminalId: '1019****'}
function cb (data) {
if (data.operationCode == 12345) {
const actualStatus = Buffer.from(data.status, "ascii")
const expectedStatus = '1'
expect(actualStatus.toString()).toBe(expectedStatus)
done()
}
}
const terminal = new InpasTerminal()
terminal.exec('/dev/ttyPos0', commandTest, cb)
})

How to change the behaviour of a mocked import?

I am quite confused with mocking in Jest an how to unit test the implementations. The thing is i want to mock different expected behaviours.
Is there any way to achieve this? as imports can be only on the top of the file and to be able to mock something it must be declared before the import. I have also tried to pass a local function so I could overwrite the behaviour but jest complains you are not allowed to pass anything local.
jest.mock('the-package-to-mock', () => ({
methodToMock: jest.fn(() => console.log('Hello'))
}));
import * as theThingToTest from '../../../app/actions/toTest'
import * as types from '../../../app/actions/types'
it('test1', () => {
expect(theThingToTest.someAction().type).toBe(types.SOME_TYPE)
})
it('test2', () => {
//the-package-to-mock.methodToMock should behave like something else
expect(theThingToTest.someAction().type).toBe(types.SOME_TYPE)
})
internally as you can imagine theThingToTest.someAction() uses the-package-to-mock.methodToMock
You can mock with a spy and import the mocked module. In your test you set how the mock should behave using mockImplementation:
jest.mock('the-package-to-mock', () => ({
methodToMock: jest.fn()
}));
import { methodToMock } from 'the-package-to-mock'
it('test1', () => {
methodToMock.mockImplementation(() => 'someValue')
})
it('test2', () => {
methodToMock.mockImplementation(() => 'anotherValue')
})
I use the following pattern:
'use strict'
const packageToMock = require('../path')
jest.mock('../path')
jest.mock('../../../../../../lib/dmp.db')
beforeEach(() => {
packageToMock.methodToMock.mockReset()
})
describe('test suite', () => {
test('test1', () => {
packageToMock.methodToMock.mockResolvedValue('some value')
expect(theThingToTest.someAction().type).toBe(types.SOME_TYPE)
})
test('test2', () => {
packageToMock.methodToMock.mockResolvedValue('another value')
expect(theThingToTest.someAction().type).toBe(types.OTHER_TYPE)
})
})
Explanation:
You mock the class you are trying to use on test suite level, make sure the mock is reset before each test and for every test you use mockResolveValue to describe what will be return when mock is returned
Another way is to use jest.doMock(moduleName, factory, options).
E.g.
the-package-to-mock.ts:
export function methodToMock() {
return 'real type';
}
toTest.ts:
import { methodToMock } from './the-package-to-mock';
export function someAction() {
return {
type: methodToMock(),
};
}
toTest.spec.ts:
describe('45006254', () => {
beforeEach(() => {
jest.resetModules();
});
it('test1', () => {
jest.doMock('./the-package-to-mock', () => ({
methodToMock: jest.fn(() => 'type A'),
}));
const theThingToTest = require('./toTest');
expect(theThingToTest.someAction().type).toBe('type A');
});
it('test2', () => {
jest.doMock('./the-package-to-mock', () => ({
methodToMock: jest.fn(() => 'type B'),
}));
const theThingToTest = require('./toTest');
expect(theThingToTest.someAction().type).toBe('type B');
});
});
unit test result:
PASS examples/45006254/toTest.spec.ts
45006254
✓ test1 (2016 ms)
✓ test2 (1 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
toTest.ts | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.443 s
source code: https://github.com/mrdulin/jest-v26-codelab/tree/main/examples/45006254
spyOn worked best for us. See previous answer:
https://stackoverflow.com/a/54361996/1708297
How to Change Mocked Functions For Different Test Scenarios
In my scenario I tried to define the mock function outside of the jest.mock which will return an error about trying to access the variable before it's defined. This is because modern Jest will hoist jest.mock so that it can occur before imports. Unfortunately this leaves you with const and let not functioning as one would expect since the code hoists above your variable definition. Some folks say to use var instead as it would become hoisted, but most linters will yell at you, so as to avoid that hack this is what I came up with:
Jest Deferred Mocked Import Instance Calls Example
This allows us to handle cases like new S3Client() so that all new instances are mocked, but also while mocking out the implementation. You could likely use something like jest-mock-extended here to fully mock out the implementation if you wanted, rather than explicitly define the mock.
The Problem
This example will return the following error:
eferenceError: Cannot access 'getSignedUrlMock' before initialization
Test File
const sendMock = jest.fn()
const getSignedUrlMock = jest.fn().mockResolvedValue('signedUrl')
jest.mock('#aws-sdk/client-s3', () => {
return {
S3Client: jest.fn().mockImplementation(() => ({
send: sendMock.mockResolvedValue('file'),
})),
GetObjectCommand: jest.fn().mockImplementation(() => ({})),
}
})
jest.mock('#aws-sdk/s3-request-presigner', () => {
return {
getSignedUrl: getSignedUrlMock,
}
})
The Answer
You must defer the call in a callback like so:
getSignedUrl: jest.fn().mockImplementation(() => getSignedUrlMock())
Full Example
I don't want to leave anything up to the imagination, although I phaked the some-s3-consumer from the actual project, but it's not too far off.
Test File
import { GetObjectCommand, S3Client } from '#aws-sdk/client-s3'
import { SomeS3Consumer } from './some-s3-consumer'
const sendMock = jest.fn()
const getSignedUrlMock = jest.fn().mockResolvedValue('signedUrl')
jest.mock('#aws-sdk/client-s3', () => {
return {
S3Client: jest.fn().mockImplementation(() => ({
send: sendMock.mockResolvedValue('file'),
})),
GetObjectCommand: jest.fn().mockImplementation(() => ({})),
}
})
jest.mock('#aws-sdk/s3-request-presigner', () => {
return {
// This is weird due to hoisting shenanigans
getSignedUrl: jest.fn().mockImplementation(() => getSignedUrlMock()),
}
})
describe('S3Service', () => {
const service = new SomeS3Consumer()
describe('S3 Client Configuration', () => {
it('creates a new S3Client with expected region and credentials', () => {
expect(S3Client).toHaveBeenCalledWith({
region: 'AWS_REGION',
credentials: {
accessKeyId: 'AWS_ACCESS_KEY_ID',
secretAccessKey: 'AWS_SECRET_ACCESS_KEY',
},
})
})
})
describe('#fileExists', () => {
describe('file exists', () => {
it('returns true', () => {
expect(service.fileExists('bucket', 'key')).resolves.toBe(true)
})
it('calls S3Client.send with GetObjectCommand', async () => {
await service.fileExists('bucket', 'key')
expect(GetObjectCommand).toHaveBeenCalledWith({
Bucket: 'bucket',
Key: 'key',
})
})
})
describe('file does not exist', () => {
beforeEach(() => {
sendMock.mockRejectedValue(new Error('file does not exist'))
})
afterAll(() => {
sendMock.mockResolvedValue('file')
})
it('returns false', async () => {
const response = await service.fileExists('bucket', 'key')
expect(response).toBe(false)
})
})
})
describe('#getSignedUrl', () => {
it('calls GetObjectCommand with correct bucket and key', async () => {
await service.getSignedUrl('bucket', 'key')
expect(GetObjectCommand).toHaveBeenCalledWith({
Bucket: 'bucket',
Key: 'key',
})
})
describe('file exists', () => {
it('returns the signed url', async () => {
const response = await service.getSignedUrl('bucket', 'key')
expect(response).toEqual(ok('signedUrl'))
})
})
describe('file does not exist', () => {
beforeEach(() => {
getSignedUrlMock.mockRejectedValue('file does not exist')
})
afterAll(() => {
sendMock.mockResolvedValue('file')
})
it('returns an S3ErrorGettingSignedUrl with expected error message', async () => {
const response = await service.getSignedUrl('bucket', 'key')
expect(response.val).toStrictEqual(new S3ErrorGettingSignedUrl('file does not exist'))
})
})
})
})
Andreas answer work well with functions, here is what I figured out using it:
// You don't need to put import line after the mock.
import {supportWebGL2} from '../utils/supportWebGL';
// functions inside will be auto-mocked
jest.mock('../utils/supportWebGL');
const mocked_supportWebGL2 = supportWebGL2 as jest.MockedFunction<typeof supportWebGL2>;
// Make sure it return to default between tests.
beforeEach(() => {
// set the default
supportWebGL2.mockImplementation(() => true);
});
it('display help message if no webGL2 support', () => {
// only for one test
supportWebGL2.mockImplementation(() => false);
// ...
});
It won't work if your mocked module is not a function. I haven't been able to change the mock of an exported boolean for only one test :/. My advice, refactor to a function, or make another test file.
export const supportWebGL2 = /* () => */ !!window.WebGL2RenderingContext;
// This would give you: TypeError: mockImplementation is not a function

Properly stubbing request/response with Sinon/Mocha

I'm relatively new to unit-testing on the backend and need some guidance as to how to unit-test the following. I'm using Mocha/Should/Sinon.
exports.get = function(req, res) {
if (req.query.example) {
return res.status(200).json({ success: true });
} else {
return res.status(400).json({error: true});
}
}
You can use Sinon's spy and stub functions to test your code like this:
const { spy, stub } = require('sinon');
const chai = require('chai');
chai.should();
describe('the get function', () => {
let status,
json,
res;
beforeEach(() => {
status = stub();
json = spy();
res = { json, status };
status.returns(res);
});
describe('if called with a request that has an example query', () => {
beforeEach(() => get({ query: { example: true } }, res));
it('calls status with code 200', () =>
status.calledWith(200).should.be.ok
);
it('calls json with success: true', () =>
json.calledWith({ success: true }).should.be.ok
);
});
describe('if called with a request that doesn\'t have an example query', () => {
beforeEach(() => get({ query: {} }, res));
it('calls status with code 400', () =>
status.calledWith(400).should.be.ok
);
it('calls json with error: true', () =>
json.calledWith({ error: true }).should.be.ok
);
});
});
In the first beforeEach call, I'm creating a stub named status and a spy named json.
The status stub is used to test if the status method of the response is called with the correct response code.
The json spy is used to test if the json method of the response is called with the correct JSON code.
Note I'm using stub for status to be able to return the mock response from any call that goes to the status method, otherwise the chaining (res.status().json()) would not work.
For json it suffices to use a simple spy, because it is at the end of the chain.

Categories

Resources