Jest does not seem to be detecting my mock calls in the catch block. Yet, if don't mock the method handleError, I'll receive an error. I have tried multiple ways of doing the returned rejected promise but no luck so far.
I've tried callbacks, Promise.reject, Promise((res, rej) => {})
Code being tested:
module.exports = class PresetDropdown {
constructor (Api, objectId, titleSort) {
this.Api = Api;
this.objectId; = objectId;
this.presets = [];
}
handleError (err) {
console.log(err)
// more functionality
}
get () {
this.Api.getMany(this.objectId)
.then((data) => {
this.handleEmpty(data);
this.isError = false;
})
.catch((err) => {
this.isError = true;
this.handleError(err);
});
}
};
Test (Jest):
test('Expect call from handleError after reject promise', async () => {
// assemble
// return the class
const Module = getModule();
const mockRejectedPromise = jest.fn(() => {
return Promise.reject(Error(mockError));
});
mockApi.getMany = mockRejectedPromise;
const module = new Module(mockApi, '1', null);
const mockHandleError = jest.fn(() => {});
PresetTemplate.handleError = mockHandleError;
// act
await PresetTemplate.get(mockScope);
// assert
expect(mockHandleError).toHaveBeenCalledTimes(1); // DOES NOT DETECT CALL
// Test CASE FAILS HERE ^
});
Since PresetDropdown class accept an Api object, you can create a mocked Api object and pass it to the class. Then mock resolved/rejected value for Api.getMany() method, so that you can test different code branches.
E.g.
presetDropdown.js:
module.exports = class PresetDropdown {
constructor(Api, objectId, titleSort) {
this.Api = Api;
this.objectId = objectId;
this.presets = [];
}
handleError(err) {
console.log(err);
}
handleEmpty(data) {
console.log(data);
}
get() {
return this.Api.getMany(this.objectId)
.then((data) => {
this.handleEmpty(data);
this.isError = false;
})
.catch((err) => {
this.isError = true;
this.handleError(err);
});
}
};
presetDropdown.test.js:
const PresetDropdown = require('./presetDropdown');
describe('71164955', () => {
test('should handle data', async () => {
const mockApi = {
getMany: jest.fn().mockResolvedValueOnce('fake data'),
};
const instance = new PresetDropdown(mockApi, '1');
await instance.get();
expect(instance.isError).toBeFalsy();
});
test('should handle error', async () => {
const mockError = new Error('fake error');
const mockApi = {
getMany: jest.fn().mockRejectedValueOnce(mockError),
};
const instance = new PresetDropdown(mockApi, '1');
await instance.get();
expect(instance.isError).toBeTruthy();
});
});
Test result:
PASS stackoverflow/71164955/presetDropdown.test.js
71164955
✓ should handle data (14 ms)
✓ should handle error (3 ms)
console.log
fake data
at PresetDropdown.handleEmpty (stackoverflow/71164955/presetDropdown.js:11:13)
console.log
Error: fake error
at Object.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/stackoverflow/71164955/presetDropdown.test.js:12:23)
at Object.asyncJestTest (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)
at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:45:12
at new Promise (<anonymous>)
at mapper (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:28:19)
at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:75:41
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at PresetDropdown.handleError (stackoverflow/71164955/presetDropdown.js:8:13)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.311 s
Related
I have a function that calls a jQuery function.
the jQuery function called dataFunc and should return an object.
I want to test the promise, not the dataFunc function.
For that, I want to mock the response that dataFunc should return
I want this row const { data } = await service.auth( buttonData ); data to return
{ access_level: 0 };
How can I do that?
This is my code:
This function with the promise I want to test:
auth(buttonData){
const myPromise = new Promise((resolve, reject) => {
const success = (e, data) => {
resolve({data, error: null});
};
const error = () => {
resolve({data: null, error: 'Error'});
};
jQuery(buttonData).dataFunc({
success,
error,
});
});
return myPromise;
}
This is what I have done in jest so far:
describe('service.test.js', () => {
beforeEach(() => {
global.jQuery = () => {
return {
dataFunc: jest.fn(() => ({access_level: 0})),
};
};
});
afterEach(() => {
jest.clearAllMocks();
});
test('should do something', async () => {
// Arrange
const service = new Service();
const approveButton = document.createElement('button');
// Act
const {data} = await service.auth(buttonData);
console.log(data);
});
});
To fulfill auth function you need either reject or resolve the value.
However, when you mock jQuery method dataFunc to return an explicit value, you override the default behavior and it never calls resolve or reject. Therefore your promise will hang.
You don't necessarily need to mock but provide the original functionality dataFunc carries or provide one that is necessary for the current test.
To fix your example you can pass the argument and call it.
global.jQuery = () => {
return {
dataFunc: ({success, error}) => {
success(jest.fn(), {access_level: 0})
},
};
};
Pretty much running the exact same code elsewhere however, this time, I am using rewire to test a non-exported function at helper.js.
When debugging the following I get to return await db.collection('foo') which returns undefined and throwing when attempting 'undefined.findOne()' naturally. My understanding is that db.collection('foo') should return mockDbConnect in this case allowing to chain findOne, which it is not doing...
Help is appreciated.
foo.test.js
const rewire = require('rewire');
const helper = rewire('../../../src/models/helper/helpers');
describe('model helpers', () => {
describe('searchValue', () => {
it('should return an instance', async () => {
const searchValue = helper.__get__('searchValue')
const mockFoo = { foo: '1' }
const fooId = '1';
const mockDbConnect = {
collection: jest.fn().mockReturnThis(),
findOne: jest.fn().mockReturnValue(mockFoo)
}
try {
const res = await searchValue(fooId, mockDbConnect);
expect(mockDbConnect.collection).toHaveBeenCalledWith('foos');
} catch (err) {
throw err;
}
});
})
})
helpers.js
const searchValue = async (fooId, db) => {
return await db
.collection('foos')
.findOne({ fooId: fooId });
};
I faced to some issue when I wanted to test my handler which publishes a message on SNS.
Here is my code:
// my handler
export const handler = (event) => {
try {
await emitDeletionComplete(classified.metadata.classifiedRequestId);
}
catch(e) {
console.error(e);
throw new Error(e);
}
}
// my SNS service
import SNS from 'aws-sdk/clients/sns';
const snsClient = new SNS({region: process.env.AWS_REGION});
export const emitDeletionComplete = async (id) => {
try {
await snsClient.publish({
Message: JSON.stringify({
type: 'DELETE_COMPLETE',
data: {
id
}
}),
TopicArn: process.env.SNS_ARN
}).promise();
} catch(err) {
console.error(err, err.stack);
throw new Error('We do not succeed to publish the message DELETE_COMPLETE to ARN: ' + process.env.SNS_ARN);
}
};
When i want to test, i try to do :
import { handler } from '../../../src/handler/dispatch-deletion-to-legacy';
import SNS from 'aws-sdk/clients/sns';
jest.mock('aws-sdk/clients/sns', () => {
return {
__esModule: true,
default: jest.fn(() => {
return {
publish: jest.fn().mockReturnThis(),
promise: jest.fn(),
}
}),
};
});
[...]
it('should delete', () => {
let sns = new SNS();
const event = {
Records: [
{
body: JSON.stringify({...some event...})
}
]
}
handler(event);
expect(sns.publish().promise).toBeCalledTimes(1);
});
Apparently, it is never called. I don't get why.Maybe my mock is completely wrong.
i'm stuck with it for few hours now...Any idea how can I mock correctly ?
EDIT 1 : https://github.com/JLGouwy/aws-sns-mock-test
thanks
In short, your instance - let sns = new SNS(); is not instance used by your production code.
In long, you have keeping track of usage of sns mock instance, jest document
This is your example, but I changed a little. I will try to explain by comments in bellow example.
Production code:
import SNS from 'aws-sdk/clients/sns';
const snsClient = new SNS({ region: process.env.AWS_REGION });
exports.handler = async (event) => {
try {
const { classifiedRequestId } = JSON.parse(event.Records[0].body); // try to get classifiedRequestId from Record body
await emitDeletionComplete(classifiedRequestId);
} catch (e) {
console.error(e);
throw new Error(e);
}
}
const emitDeletionComplete = async (id) => { // in the same file with handler function
try {
await snsClient.publish({
Message: JSON.stringify({
type: 'DELETE_COMPLETE',
data: {
id
}
}),
TopicArn: process.env.SNS_ARN
}).promise();
} catch (err) {
console.error(err, err.stack);
throw new Error('We do not succeed to publish the message DELETE_COMPLETE to ARN: ' + process.env.SNS_ARN);
}
};
Spec file
import SNS from 'aws-sdk/clients/sns';
import { handler } from "."; // handler function in the same directory
const mockPromise = jest.fn(); // mock for deep function - promise
jest.mock('aws-sdk/clients/sns', () => {
// return a function as a constructor
return jest.fn().mockImplementation(function () { // "normal function" not arrow function
this.publish = jest.fn(() => ({ // mock publish function
promise: mockPromise, // returns an object what includes promise property
}));
});
});
describe("handler function", () => {
it("should publish delete command", async () => {
const classifiedRequestId = "12345";
const event = {
Records: [
{
body: JSON.stringify({ // input data
classifiedRequestId,
}),
}
]
};
await handler(event);
// get instance of SNS, an instance has been created in production code
const snsMocked = SNS.mock.instances[0];
// expect publish function will be call with expected parameter
expect(snsMocked.publish).toHaveBeenCalledWith(
{
Message: JSON.stringify({
type: 'DELETE_COMPLETE',
data: {
id: classifiedRequestId,
}
}),
TopicArn: process.env.SNS_ARN
}
);
// expect promise function will be call too
expect(mockPromise).toHaveBeenCalled();
})
});
I'm trying to create a mock for a function createClient where it should return a specific object.
However, for some reason the mock is ignored and it runs the function instead of receiving the mock value.
authorization.js
// some requires here
const createClient = req => {
if (!(req.user && req.user.access_token)) {
throw new Error('Not authorized');
}
...
return { ... }
}
const getUser = async client => { ... }
module.exports = options => {
...
createClient(req) is called here
...
}
authorization.test.js
import authorization from '../../server/middlewares/authorization';
describe('authorization.js', () => {
it('Should do something', async done => {
authorization.createClient = jest.fn(() => ({
client: 'test',
}));
// ACT
const authorizationMiddleware = authorization();
const result = await authorizationMiddleware(someOptions);
// ASSERT
expect(result).toBe('authorized');
done();
});
The error
It seems that the mock for createClient is not working as I wanted to be. It should return the object { client: 'test' }
Your code is incomplete, so I try to give a demo for your case. If you want to mock a private variable in the module scope, the createClient function for your case. You can use rewire package to do that.
E.g.
authorization.js
let createClient = (req) => {
if (!(req.user && req.user.access_token)) {
throw new Error('Not authorized');
}
function getUser() {
return 'real user';
}
return { getUser };
};
const getUser = async (client) => {
return client.getUser();
};
module.exports = (options) => {
const client = createClient(options.req);
return () => getUser(client);
};
authorization.test.js:
const rewire = require('rewire');
describe('61076881', () => {
it('should get user', async () => {
const authorization = rewire('./authorization');
const mClient = { getUser: jest.fn().mockReturnValueOnce('fake user') };
const mCreateClient = jest.fn(() => mClient);
authorization.__set__('createClient', mCreateClient);
const options = { req: { user: { access_token: '123' } } };
const authorizationMiddleware = authorization(options);
const user = await authorizationMiddleware();
expect(user).toEqual('fake user');
expect(mCreateClient).toBeCalledWith(options.req);
expect(mClient.getUser).toBeCalledTimes(1);
});
});
unit test results:
PASS stackoverflow/61076881/authorization.test.js (7.601s)
61076881
✓ should get user (10ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.54s, estimated 9s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61076881
I'm a total beginner with Jest.
I've got a UserService using Dependency Injection.
public async getAll() {
const userRecords = await this.userModel.find().select('name').catch((e) => {
throw new HttpException(500, 'Error while fetching users.', e)
});
return <[IUser]>userRecords;
}
I would like to test this feature. Here are the tests I could run:
Calling the route, and checking if the resulting JSON is OK
Ggetting DB content, and checking if it is as expected
Just test the getAll function
I think 1 and 2 are obvious, and cover different kind of things. 1 covers the request part, 2 covers the DB part. But what about number 3? How to "just test" the getAll function?
I've tried this:
const userModel = {
find: (user) => {
return [
{ id: 'user1' },
{ id: 'user2' }
]
},
};
const userService = new UserService(userModel);
const userRecords = await userService.getAll();
expect(argumentRecord).toBeDefined();
But obviously it's failing because select is undefined.
Should I also mock select()? Should I organize my code differently?
If were to write this test I would mock the functions using jest.fn(implementation) so that expectations can be enforced on the function calls.
const userQuery = {
select: jest.fn(() => Promise.resolve([]))
};
const userModel = {
find: jest.fn(() => userQuery)
};
const userService = new UserService(userModel);
const userRecords = await userService.getAll();
expect(userRecords).toEqual([]);
expect(userModel.find).toHaveBeenCalled();
expect(userQuery.select).toHaveBeenCalledWith('name');
Performing expectations on the function calls may sound like overkill, but it explicitly verifies that the mock is actually being used by getAll.
I would also structure the tests in such a way that I can test the various code paths without re-implementing the entire mock.
describe('getAll()', () => {
let userQuery, userModel, userService;
beforeEach(() => {
userQuery = {
select: jest.fn(() => Promise.resolve([]))
};
userModel = {
find: jest.fn(() => userQuery)
};
userService = new UserService(userModel);
});
afterEach(() => {
expect(userModel.find).toHaveBeenCalled();
expect(userQuery.select).toHaveBeenCalledWith('name');
});
it('should get the user names', async () => {
const users = [{
name: 'john'
}, {
name: 'jane'
}];
userQuery.select.mockImplementation(() => Promise.resolve(users));
await expect(userService.getAll()).resolves.toBe(users);
});
it('should handle errors', async () => {
const error = new Error('Fake model error');
userQuery.select.mockImplementation(() => Promise.reject(error));
await expect(userService.getAll()).rejects.toMatch({
status: 500,
message: 'Error while fetching users.',
cause: error
});
});
});
This code is untested, so it may not work correctly, but hopefully it outlines the idea sufficiently.
While this is not directly related to your question I would avoid mixing async/await with traditional promise handling.
public async getAll() {
try {
return <[IUser]> await this.userModel.find().select('name');
} catch (e) {
throw new HttpException(500, 'Error while fetching users.', e)
}
}
Yes, you should mock select. And not only that, but everything that is used inside the function and test if they are executed properly. I would do this:
class SomeClass {
public async getAll() {
const userRecords = await this.userModel.find().select('name').catch(this.errorHandler);
return <[IUser]>userRecords;
}
public errorHandler(e) {
throw new HttpException(500, 'Error while fetching users.', e);
}
}
// this is just an example, it should be the same type as your expected returned output
const whatever = Math.random();
const fakeCatch = jest.fn(() => whatever);
const fakeSelect = jest.fn(() => {
return {
catch: fakeCatch
}
});
const fakeFind = jest.fn(() => {
return {
select: fakeSelect
};
});
const fakeUserModel = {
find: fakeFind,
}
const userService = new UserService(fakeUserModel);
const userRecords = await userService.getAll();
// should return the correct result
expect(userRecords).toEqual(whatever);
// should execute find
expect(fakeFind).toHaveBeenCalledTimes(1);
// should execute select with 'name' parameter
expect(fakeSelect).toHaveBeenCalledTimes(1);
expect(fakeSelect).toHaveBeenCalledWith('name');
// should execute catch with this.errorHandler
expect(fakeCatch).toHaveBeenCalledWith(userService.errorHandler);