Jest mock function not working as expected - javascript

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

Related

Jest does not detect my mock call in promise .catch block

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

how to unit test if handler function is called when using middy

im using an http request function as the handler function in middy and then use the ssm middleware to fetch some ssm parameters before initiating the http request.
like this:
const makeThirdPartyServiceRequest = middy(async ({ params }) => {
logger.info(`SENDING Request to ${endpoint} API`)
const url = `https://someurltoathirdpartyservice`
const options = {
method: 'POST',
body: params
}
return helpers.makeRequest(url, options)
})
makeThirdPartyServiceRequest.use(ssm(......))
However in my jest unit test Im trying to mock makeThirdPartyServiceRequest and explicitly say it should resolve to a value:
jest.mock('../src/thirdPartyService', () => ({
__esModule: true,
default: {
...(jest.requireActual('../src/thirdPartyService') as { default: {} }).default,
makeThirdPartyServiceRequest: jest.fn()
}
}))
export {}
import thirdPartyService from '../src/thirdPartyService'
And then in the test i say:
describe('makeThirdPartyServiceRequest()', () => {
it('should makeThirdPartyServiceRequest', async () => {
// Given
// })
const mockedThirdPartyServiceRequest = mocked(thirdPartyService.makeThirdPartyServiceRequest).mockResolvedValue({})
// When
const result = await thirdPartyService.makeThirdPartyServiceRequest(something)
// Then
expect(mockedThirdPartyServiceRequest).toHaveBeenCalledTimes(1)
expect(mockedThirdPartyServiceRequest.mock.calls[0][0].params.toString()).toBe(expectedParams)
})
})
However for some reason the middy middleware is still being invoked, which i clearly dont want and i have tried to mock away... what am i doing wrong?
You need to mock middy instead, to make it becomes a useless function. That function recipe a function as a parameter and return that parameter.
import thirdPartyService from '../src/thirdPartyService'
jest.mock('#middy/core', () => {
return (handler) => {
return {
use: jest.fn().mockReturnValue(handler), // ...use(ssm()) will return handler function
}
}
})
describe('thirdPartyService()', () => {
beforeEach(() => {
jest.spyOn(helpers, 'makeRequest') // spy on helpers unit
})
describe('makeThirdPartyServiceRequest', () => {
it('should make a request with correct parameters', async () => {
// Given
const url = `https://someurltoathirdpartyservice`
const params = 'any params'
const apiResponse = 'any response'
mocked(helpers.makeRequest).mockResolvedValue(apiResponse)
// When
const actual = await thirdPartyService.makeThirdPartyServiceRequest(params)
// Then
expect(actual).toBe(apiResponse)
expect(helpers.makeRequest).toHaveBeenCalledWith(
url,
{
method: 'POST',
body: params
}
)
})
})
})
hoangdv answer is also valid, but i will answer as well how i continued.
if you completely want to mock middy you mock like following:
jest.mock('#middy/core', () => {
return (handler) => {
return {
use: jest.fn().mockImplementation(() => {
// ...use(ssm()) will return handler function
return {
before: jest.fn().mockReturnValue(handler)
}
})
}
}
})
However if you dont want to completely mock middy, you can instead mock the async getInternal function from middy/util called in before like this:
jest.doMock('#middy/util', () => ({
...(jest.requireActual('#middy/util') as {}),
getInternal: jest.fn()
}))
import { getInternal } from '#middy/util'
and then in the test
describe('thirdPartyService()', () => {
beforeEach(() => {
jest.spyOn(helpers, 'makeRequest') // spy on helpers unit
})
describe('makeThirdPartyServiceRequest', () => {
it('should make a request with correct parameters', async () => {
// Given
const url = `https://someurltoathirdpartyservice`
const params = 'any params'
const apiResponse = 'any response'
mocked(getInternal).mockResolvedValue({
twilioSecrets: { accountSid: 'someSID', serviceId:
'someServiceID', token: 'someToken' }
})
mocked(helpers.makeRequest).mockResolvedValue(apiResponse)
// When
const actual = await thirdPartyService.makeThirdPartyServiceRequest(params)
// Then
expect(actual).toBe(apiResponse)
expect(helpers.makeRequest).toHaveBeenCalledWith(
url,
{
method: 'POST',
body: params
}
)
})
})
})
this will mock the async part of middy.

How to mock SQL Server connection pool using Jest?

I am trying to write a jest unit test for a function that utilizes mssql.
import * as sql from "mssql";
let pool: sql.ConnectionPool = null;
export async function handler() {
if (pool === null) {
try {
pool = new sql.ConnectionPool("");
await pool
.request()
.input("SomeInput", sql.NVarChar(255), "input")
.execute("SomeStoredProcedure");
} catch (err) {
console.error(err);
}
}
}
What would be the simplest way to mock the sql methods and assert they have been called?
import { handler } from "../src/main";
describe("test handler", () => {
it("should succeed", async () => {
const requestFn = jest.fn();
const executeFn = jest.fn();
const inputFn = jest.fn();
// Mock mssql connection pool with above functions
// *????????*
await handler();
// Expect the functions have been called
expect(requestFn).toHaveBeenCalled();
expect(executeFn).toHaveBeenCalled();
expect(inputFn).toHaveBeenCalled();
});
});
Sandbox
You can mock the mssql package by using an jest ES6 Class Mocks. You can achive that by using:
const mockExecute = jest.fn();
const mockInput = jest.fn(() => ({ execute: mockExecute }));
const mockRequest = jest.fn(() => ({ input: mockInput }));
jest.mock('mssql', () => ({
ConnectionPool: jest.fn(() => ({
request: mockRequest
})),
NVarChar: jest.fn()
}));
Have a look at the Stackblitz project and run jest in the terminal. You should see that the tests are passing.
You need to mock the return value of each function in the chain. You can do this using jest.fn().mockImplementation(implementation)
Expanding you example to use this give us the following
import { handler } from "../src/main";
let pool;
describe("test handler", () => {
it("should succeed", async () => {
const requestFn = jest.fn();
const executeFn = jest.fn();
const inputFn = jest.fn();
pool = {
request: requestFn,
execute: executeFn,
inputFn: inputFn,
};
requestFn.mockImplementation(() => pool);
executeFn.mockImplementation(() => pool);
inputFn.mockImplementation(() => pool);
await handler();
// Expect the functions have been called
expect(requestFn).toHaveBeenCalled();
expect(executeFn).toHaveBeenCalled();
expect(inputFn).toHaveBeenCalled();
});
});

Why is this not working? Testing non exported-function with Rewire / Jest

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

Jest: How to properly test a Javascript service using MongoDB

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

Categories

Resources