service.js
Class ClassWithConstructor {
constructor(arg1, arg2) {
super();
this.param1 = arg1;
this.param2 = arg2;
}
getInfo() {
//returns promise
}
}
controller.js
export function getData(req, res) {
const svc = new ClassWithConstructor.ClassWithConstructor(req.user.param1, req.user.param2);
svc.getInfo()
.then(result => {
return respondWithSearchResult(res, 200, result, 'success');
})
.catch(err => {
//return error
});
}
For unit test getData() method I need to mock the line const svc = new ClassWithConstructor.ClassWithConstructor(req.user.param1, req.user.param2);
I am using Sinon for mocking. I have been trying for a long. Can someone help me out?
Related
I'm using jest with nodejs and sequelize for my models. For my testing, I wanted to mock the returned value of findAll to cover test scenarios. Sorry if this is a very newbie question but I'm at dead-end on this one.
init-models.js
module.exports = function initModels(sequelize) {
//model relationship code here
...
...
//end of model relationship code
return {
records,
anotherModel,
alsoAnotherModel
};
};
repository.js
const sequelize = require('../sequelize');
const initModels = require('../model/init-models');
let {
records,
anotherModel,
alsoAnotherModel
} = initModels(sequelize);
const fetchRecords = async () => {
console.info('Fetching records...');
return await records.findAll({sequelize parameters here});
}
repository.test.js This will work but needs the flexibility to mock findAll() return value/or throw Error
const repository = require('../../../src/db/repository/repository');
const initModels = require('../../../src/db/model/init-models');
jest.mock('../../../src/db/model/init-models', () => {
return function() {
return {
records: {
findAll: jest.fn().mockImplementation(() => [1,2,3])
}
//the rest of the code for other models
}
}
});
describe('fetchRecords', () => {
beforeEach(()=> {
});
test('should return correct number of records', async () => {
const result = await repository.fetchRecords();
expect(result.size).toStrictEqual(3); //test passed
});
})
To allow mocking of results of findAll, I've tried extracting it so I can change the result per test scenario, but it was not working. What did I missed?
const mockRecordsFindAll = jest.fn();
jest.mock('../../../src/db/model/init-models', () => {
return function() {
return {
records: {
findAll: () => mockRecordsFindAll
}
//the rest of the code for other models
}
}
});
describe('fetchRecords', () => {
beforeEach(()=> {
mockRecordsFindAll.mockReset()
});
test('should return correct number of records', async () => {
mockRecordsFindAll.mockImplementation(() => [1,2,3]); //should expect length 3
const result = await repository.fetchRecords();
expect(result.size).toStrictEqual(3); //fails, findAll was not mocked
});
})
The issue is mockRecordsFindAll is being returned instead of executed.
As #Gid machined just returning mockRecordsFindAll causes initialization issues (due to hoisting).
The solution for this case is using the decorator pattern to allow mockRecordsFindAll to be initialized afterward.
const mockRecordsFindAll = jest.fn();
jest.mock('../../../src/db/model/init-models', () => {
return function() {
return {
records: {
findAll: function () {
return mockRecordsFindAll.call(this, arguments);
}
}
}
}
});
describe('fetchRecords', () => {
...
})
I'm getting below errors. Why I'm receiving response as undefined from the service?
Is there anything wrong I did for providing mock implementations?
Service:
export class SaveDataService{
async save() : Promise<any> {
try{
return this.someFunction()
} catch(ex){
throw new Error('some error occured')
}
}
async someFunction() : Promise<any>{
const response = {
"file" : "<htm><body>This is sample response</body></html>"
}
return Promise.resolve(response);
}
}
Test/Spec file:
import { SaveDataService } from "./save-data.service";
jest.mock('./save-data.service')
describe('tests for SaveDataService', () => {
it('when save method is called and success result is returned', async () => {
let mockSaveDataServiceSomeFunction = jest.fn().mockImplementation(() => {
return Promise.resolve('Success Result')
});
SaveDataService.prototype.someFunction = mockSaveDataServiceSomeFunction;
let spy = jest.spyOn(SaveDataService.prototype, 'someFunction');
let service = new SaveDataService();
let data = await service.save()
expect(data).toEqual('Success Result')
expect(spy).toHaveBeenCalled()
})
it('when save method is called and error is returned', async () => {
let mockSaveDataServiceSomeFunction = jest.fn().mockImplementation(() => {
throw new Error('ERROR')
});
SaveDataService.prototype.someFunction = mockSaveDataServiceSomeFunction;
let spy = jest.spyOn(SaveDataService.prototype, 'save');
let service = new SaveDataService();
service.save()
expect(spy).toThrowError('ERROR')
})
})
A mock replaces the dependency. You set expectations on calls to the dependent object, set the exact return values it should give you to perform the test you want, and/or what exceptions to throw so that you can test your exception handling code.
In this scenario, you are mocking save-data.service by calling jest.mock('./save-data.service'). So that your class may looks like this:
async save() : Promise<any> {
// do nothing or undefined
}
async someFunction() : Promise<any> {
// do nothing or undefined
}
So you must implement the body yourself to expect what exactly you want the method/function to do for you. You are mocking only the someFunction:
...
let mockSaveDataServiceSomeFunction = jest.fn().mockImplementation(() => {
return Promise.resolve('Success Result')
});
SaveDataService.prototype.someFunction = mockSaveDataServiceSomeFunction;
...
So when you call the save() method you still get nothing/undefined.
You are overwriting the whole behavior of the service that I think your test may not be useful. But you can fix your test this way:
import { SaveDataService } from "./save-data.service";
jest.mock('./save-data.service');
describe('tests for SaveDataService', () => {
beforeEach(() => {
SaveDataService.mockClear();
});
it('when save method is called and success result is returned', async () => {
const spy = jest
.spyOn(SaveDataService.prototype, 'save')
.mockImplementation(async () => Promise.resolve('Success Result'));
const service = new SaveDataService();
const data = await service.save();
expect(data).toEqual('Success Result');
expect(spy).toHaveBeenCalled();
})
it('when save method is called and error is returned', async () => {
const spy = jest
.spyOn(SaveDataService.prototype, 'save')
.mockImplementation(() => {
throw new Error('ERROR');
});
const service = new SaveDataService();
expect(service.save).toThrowError('ERROR');
expect(spy).toHaveBeenCalled();
});
});
I'm trying to create a unit test for the following service, using Sinon.
as you can see the "_createRedisConnection" is called on the constructor, so in the unit test I must mock the Redis connection.
import { inject, injectable } from "inversify";
import { TYPES } from "../../inversify/types";
import { Logger } from "winston";
import { Config } from "../../interfaces/config.interface";
import { BaseService } from "../base.service";
import * as Redis from "ioredis";
import { HttpResponseError } from "../../interfaces/HttpResponseError.interface";
import { BaseResponse } from "../../interfaces/BaseResponse.interface";
#injectable()
export class RedisService extends BaseService {
private _redisClient;
private _isRedisConnected: boolean;
constructor(#inject(TYPES.Logger) private logger: Logger,
#inject(TYPES.Config) private config: Config) {
super(logger, config);
this._isRedisConnected = false;
this._createRedisConnection();
}
public async set(key, value, epu, receivedTtl): Promise<BaseResponse> {
if (this._isRedisConnected) {
const encryptedKey = this.createEncryptedKey(epu, key);
if (!encryptedKey || !value) {
throw new HttpResponseError("General error", "Missing attributes in request body", 422);
}
const ttl = this.limitTtl(receivedTtl);
let response;
if (ttl >= 0) {
await this._redisClient.setex(encryptedKey, ttl, value)
.then(() => {
response = new BaseResponse("success", "Data saved successfully", ttl);
})
.catch((errorMessage: string) => {
throw new HttpResponseError("General error", `Error while saving data. err = ${errorMessage}`, 500);
});
} else {
await this._redisClient.set(encryptedKey, value)
.then(() => {
response = new BaseResponse("success", "Data saved successfully", ttl);
})
.catch((errorMessage: string) => {
throw new HttpResponseError("General error", `Error while saving data. err = ${errorMessage}`, 500);
});
}
return response;
}
throw new HttpResponseError("General error", "Cache is not responding", 503);
}
private _createRedisConnection(): void {
this._redisClient = new Redis({
sentinels: [{ host: this.config.redisConfig.host, port: this.config.redisConfig.port }],
name: "mymaster",
dropBufferSupport: true,
});
this._redisClient.on("connect", () => {
this._isRedisConnected = true;
});
this._redisClient.on("error", (errorMessage: string) => {
this._isRedisConnected = false;
});
}
}
My problem is with mocking the Redis connection. I'm trying stub the 'connect' event, but while debugging it I see that the event never triggered (even not the error event).
import "reflect-metadata";
import { expect } from "chai";
import { Logger } from "winston";
import * as Redis from "ioredis";
import { stub } from "sinon";
import { RedisService } from "./redis.service";
import { config } from "../../config";
class LoggerMock {
public info(str: string) { }
public error(str: string) { }
}
describe("RedisService Service", () => {
const redisStub = stub(Redis.prototype, "connect").returns(Promise.resolve());
const logger = new LoggerMock() as Logger;
const redisService = new RedisService(logger, config);
it("Should success set data", async () => {
const redisClientStub = stub(Redis.prototype, "set").resolves(new Promise((resolve, reject) => { resolve('OK'); }));
const result = await redisService.set("key", "value", "epu", -1);
expect(result.message).to.equals("success");
expect(result.response).to.equals("Data saved successfully");
redisClientStub.restore();
redisStub.restore();
});
});
What is the right way to test this service? why no event is triggered when stubbing this way?
Thanks
This is an example to how to stub ioredis Redis.prototype.connect.
// File test.js
const { expect } = require('chai');
const Redis = require('ioredis');
const sinon = require('sinon');
describe('connection', function () {
it('should emit "connect" when connected', function (done) {
// Create stub on connect.
const stubRedisConnect = sinon.stub(Redis.prototype, 'connect');
stubRedisConnect.callsFake(async function () {
// This will trigger connect event.
this.setStatus('connect');
});
const redis = new Redis();
redis.on('connect', function () {
// Do not forget to restore the stub.
stubRedisConnect.restore();
done();
});
});
});
When I run it on my terminal:
$ npx mocha test.js
connection
✓ should emit "connect" when connected
1 passing (6ms)
If the test stub failed, there will be default timeout error for 2000ms because done not get called.
I'm trying to write unit test where I need to mock response of method from cognito service -
CognitoIdentityServiceProvider
I have the following working code calling the adminInitiateAuth operation
import * from AWS from 'aws-sdk'
const cognito = new AWS.CognitoIdentityServiceProvider();
const response = await cognito.adminInitiateAuth(expectedParams).promise();
// main functionality I want to test
and I want to have a spec where I try to mock this service as prerequisites
const mockResponse = {
AuthenticationResult: {
AccessToken: 'expected-token'
}
}
jest.mock('aws-sdk', () => {
return {
CognitoIdentityServiceProvider: {
adminInitiateAuth: () => {
return mockResponse;
}
}
}
});
this returns me an error
AWS.CognitoIdentityServiceProvider is not a constructor
How this can not be a constructor?
Do you have any ideas how to mock it?
I figured it out. It may be useful to someone
jest.mock('aws-sdk', () => {
return {
CognitoIdentityServiceProvider: class {
adminInitiateAuth() {
return this;
}
promise() {
return Promise.resolve(mockResponse);
}
}
}
});
Here's another way to do it if we want to use spyOn to mock a specific function:
const cognotoIdentityServiceProvider = Object.getPrototypeOf(new AWS.CognitoIdentityServiceProvider());
const stub = jest.spyOn(cognotoIdentityServiceProvider, 'adminDeleteUser').mockReturnValue({
promise: () => Promise.resolve('bob johnson'),
});
Hope this will help
jest.mock("aws-sdk", () => {
const cognito = { listUsers: jest.fn() };
return {
CognitoIdentityServiceProvider: jest.fn(() => cognito),
config: {
update: jest.fn(),
},
};
});
const mCognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider();
mCognitoIdentityServiceProvider.listUsers.mockImplementationOnce(() => {
return {
promise() {
return Promise.resolve('your mock data');
},
};
});
Using the AWS SDK for JavaScript v3
jest.mock('#aws-sdk/client-cognito-identity-provider', () => {
return {
CognitoIdentityProviderClient: class {
send() {
return mockCognitoResponseAddGroup
}
promise() {
return Promise.resolve({})
}
},
CreateGroupCommand: class {},
}
})
mocha + chai + ts-mockito is executing unit-test of the node.js application.
In order to test the Service class, we mock the method of the Repository class which is called in the Service class.
Running will not work as expected.
I expect ErrorDto, but the string "TypeError: Can not read property 'then' of null" will be returned.
Please tell me something is wrong.
Test code.
let mockedRepository: MyRepository = tsmockito.mock(MyRepository);
describe("MyService Test", () => {
it("getAll() test", async () => {
const errorDto = new ErrorDto();
errorDto.addMessage("test message");
tsmockito.when(mockedRepository.getAll()).thenCall(() => {
return new Promise((resolve, reject) => {
reject(errorDto);
});
});
let mockedRepositoryInstance: MyRepository = tsmockito.instance(mockedRepository);
Container.set(MyRepository, mockedRepositoryInstance);
let service = new MyService();
let res = await service.getAll();
// I expect ErrorDto for res,
// but "TypeError: Can not read property 'then' of null" is returned.
if (res instanceof ErrorDto) {
// Do not reach below
let msg = res.getMessages();
expect(msg[0]).to.eql("test message");
} else {
// Test failure
expect(true).to.false;
}
});
});
Service class to be tested
#Service()
export default class MyService {
#Log()
public getAll() {
const repository = Container.get(MyRepository);
return new Promise<MyServiceTreeDto[]>((resolve, reject) => {
repository.getAll()
.then((res: MyTreeDomain[]) => {
const resDto = new Array<MyServiceTreeDto>();
//do something...
resolve(resDto);
}).catch((err) => {
reject(err);
});
});
}
}
Repository class to be mocked
#Service()
export default class MyRepository {
public async getAll(domain: MyDomain): Promise<MyTreeDomain[]> {
try {
let res: MyTreeDomain[] = new Array<MyTreeDomain>();
//do async process
return res;
} catch (err) {
if (err instanceof ErrorDto) {
throw err;
} else {
const errorDto = new ErrorDto();
errorDto.addMessage("error!");
throw errorDto;
}
}
}
}