I'm writing a test in jest for a module which uses a constant from a different module.
I want to set a different value for it for every test case, but I don't seem to be able to do so.
The test file:
import { Request, Response } from 'express';
const activityConsumer = require('../../src/utils/activity.consumer');
const mockRequest = {
params: {
activityArn: 'activityArn'
}
} as Request;
const mockedJsonFunction = jest.fn();
const mockResponse: any = {
json: jest.fn(),
status: jest.fn().mockReturnValue({ json: mockedJsonFunction }),
} as Response;
let stopConsumerMock;
describe('consumer handler', () => {
beforeAll(() => {
stopConsumerMock = activityConsumer.stopConsumer = jest.fn().mockReturnValue(1);
});
beforeEach(() => {
jest.resetModules();
});
afterEach(() => {
stopConsumerMock.mockClear();
mockResponse.json.mockClear();
});
describe('stopConsumingHandler', () => {
it('Should return success true and not call stopConsumer when no consumer exists', () => {
activityConsumer.consumer = undefined;
const { stopConsumingHandler } = require ('../../src/handlers/consumer.handlers');
stopConsumingHandler(mockRequest, mockResponse);
expect(stopConsumerMock.mock.calls.length).toEqual(0);
expect(mockResponse.json.mock.calls.length).toEqual(1);
expect(mockResponse.json).toHaveBeenCalledWith({ success: true });
});
it('Should return success true and call stopConsumer when consumer exists', () => {
activityConsumer.consumer = true;
const { stopConsumingHandler } = require ('../../src/handlers/consumer.handlers');
stopConsumingHandler(mockRequest, mockResponse);
expect(stopConsumerMock.mock.calls.length).toEqual(1);
expect(mockResponse.json.mock.calls.length).toEqual(1);
expect(mockResponse.json).toHaveBeenCalledWith({ success: true });
});
});
});
I want to replace the value of activityConsumer.consumer and then reload the consumer.handlers module but the re-assignment and reload does not seem to have any effect.
Please advise on how can I write this test properly.
Try this way, using jest.mock to modify import value of activityConsumer
import { Request, Response } from 'express';
// const activityConsumer = require('../../src/utils/activity.consumer');
const mockRequest = {
params: {
activityArn: 'activityArn'
}
} as Request;
const mockedJsonFunction = jest.fn();
const mockResponse: any = {
json: jest.fn(),
status: jest.fn().mockReturnValue({ json: mockedJsonFunction }),
} as Response;
let stopConsumerMock;
describe('consumer handler', () => {
beforeAll(() => {
// stopConsumerMock = activityConsumer.stopConsumer = jest.fn().mockReturnValue(1);
stopConsumerMock = jest.fn().mockReturnValue(1);
});
beforeEach(() => {
jest.resetModules(); // important line
});
afterEach(() => {
stopConsumerMock.mockClear();
mockResponse.json.mockClear();
});
describe('stopConsumingHandler', () => {
it('Should return success true and not call stopConsumer when no consumer exists', () => {
// activityConsumer.consumer = undefined;
// mock by this way
jest.mock('../../src/utils/activity.consumer', () => ({
consumer: undefined,
stopConsumer: stopConsumerMock,
}));
const { stopConsumingHandler } = require('../../src/handlers/consumer.handlers');
stopConsumingHandler(mockRequest, mockResponse);
expect(stopConsumerMock.mock.calls.length).toEqual(0);
expect(mockResponse.json.mock.calls.length).toEqual(1);
expect(mockResponse.json).toHaveBeenCalledWith({ success: true });
});
it('Should return success true and call stopConsumer when consumer exists', () => {
// activityConsumer.consumer = true;
// mock by this way
jest.mock('../../src/utils/activity.consumer', () => ({
consumer: true, // mock value for consumer
stopConsumer: stopConsumerMock,
}));
const { stopConsumingHandler } = require('../../src/handlers/consumer.handlers');
stopConsumingHandler(mockRequest, mockResponse);
expect(stopConsumerMock.mock.calls.length).toEqual(1);
expect(mockResponse.json.mock.calls.length).toEqual(1);
expect(mockResponse.json).toHaveBeenCalledWith({ success: true });
});
});
});
Related
How to change the implementation of a mock depending on the test.
this is the code I am trying to unit test.
open(url: string, target: string = '_self'): void {
const win = this.document.defaultView?.open(url, target);
win?.focus();
}
And below my unit test
const MockDocument = {
location: { replace: jest.fn() },
defaultView: undefined
};
const createService = createServiceFactory({
service: RedirectService,
providers: [
{ provide: DOCUMENT, useValue: MockDocument }
]
});
My strategy is to set defaultView to undefined for the first test. And inside the next test, I would change the implementation to contain the open function, like this
const defaultView = {
open: jest
.fn()
.mockImplementation((url: string, target: string = '_self') => {
url;
target;
return { focus: jest.fn().mockImplementation(() => {}) };
})
};
const MockDocument = {
location: { replace: jest.fn() },
defaultView: defaultView
};
How do I change the implementation depending on the test?
Thanks for helping
EDIT
it('should not call "open" if defaultView is not truthy', () => {
spectator.service.openFocus('foo');
expect(mockDocument.defaultView).not.toBeTruthy();
});
it('should call "open" if defaultView is truthy', () => {
//CHANGE THE IMPLEMENTATION HERE...
const spy = jest.spyOn(mockDocument.defaultView, 'open')
spectator.service.openFocus('foo');
expect(spy).toHaveBeenCalled();
});
You can set defaultView up as a variable and change the variable speed test.
let defaultView: any;
const MockDocument = {
location: { replace: jest.fn() },
defaultView};
beforeEach(() => {
...
});
it('should not call "open" if defaultView is not truthy', () => {
defaultView = undefined;
expect(mockDocument.defaultView).not.toBeTruthy();
});
it('should call "open" if defaultView is truthy', () => {
defaultView = {
open: jest
.fn()
.mockImplementation((url: string, target: string = '_self') => {
url;
target;
return { focus: jest.fn().mockImplementation(() => {}) };
})
};
const spy = jest.spyOn(mockDocument.defaultView, 'open')
expect(spy).toHaveBeenCalled();
});
I have the following lambda handler to unit test. It uses a library #org/aws-connection which has a function mysql.getIamConnection which simply returns a knex connection.
Edit: I have added the mysql.getIamConnection function to the bottom of the post
Edit: If possible, I'd like to do the testing with only Jest. That is unless it becomes to complicated
index.js
const {mysql} = require('#org/aws-connection');
exports.handler = async (event) => {
const connection = await mysql.getIamConnection()
let response = {
statusCode: 200,
body: {
message: 'Successful'
}
}
try {
for(const currentMessage of event.Records){
let records = JSON.parse(currentMessage.body);
await connection.transaction(async (trx) => {
await trx
.table('my_table')
.insert(records)
.then(() =>
console.log(`Records inserted into table ${table}`))
.catch((err) => {
console.log(err)
throw err
})
})
}
} catch (e) {
console.error('There was an error while processing', { errorMessage: e})
response = {
statusCode: 400,
body: e
}
} finally {
connection.destroy()
}
return response
}
I have written some unit tests and I'm able to mock the connection.transaction function but I'm having trouble with the trx.select.insert.then.catch functions. H
Here is my testing file
index.test.js
import { handler } from '../src';
const mocks = require('./mocks');
jest.mock('#org/aws-connection', () => ({
mysql: {
getIamConnection: jest.fn(() => ({
transaction: jest.fn(() => ({
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockReturnThis()
})),
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockReturnThis(),
destroy: jest.fn().mockReturnThis()
}))
}
}))
describe('handler', () => {
test('test handler', async () =>{
const response = await handler(mocks.eventSqs)
expect(response.statusCode).toEqual(200)
});
});
This test works partially but it does not cover the trx portion at all. These lines are uncovered
await trx
.table('my_table')
.insert(records)
.then(() =>
console.log(`Records inserted into table ${table}`))
.catch((err) => {
console.log(err)
throw err
})
How can set up my mock #org/aws-connection so that it covers the trx functions as well?
Edit:
mysql.getIamConnection
async function getIamConnection (secretId, dbname) {
const secret = await getSecret(secretId)
const token = await getToken(secret)
let knex
console.log(`Initialzing a connection to ${secret.proxyendpoint}:${secret.port}/${dbname} as ${secret.username}`)
knex = require('knex')(
{
client: 'mysql2',
connection: {
host: secret.proxyendpoint,
user: secret.username,
database: dbname,
port: secret.port,
ssl: 'Amazon RDS',
authPlugins: {
mysql_clear_password: () => () => Buffer.from(token + '\0')
},
connectionLimit: 1
}
}
)
return knex
}
Solution
#qaismakani's answer worked for me. I wrote it slightly differently but the callback was the key. For anyone interested here is my end solution
const mockTrx = {
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockResolvedValue()
}
jest.mock('#org/aws-connection', () => ({
mysql: {
getIamConnection: jest.fn(() => ({
transaction: jest.fn((callback) => callback(mockTrx)),
destroy: jest.fn().mockReturnThis()
}))
}
}))
Updating your mock to look like this might do the trick:
const { mysql } = require("#org/aws-connection");
jest.mock("#org/aws-connection", () => ({
mySql: {
getIamConnection: jest.fn()
}
}));
const mockTrx = {
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockResolveValue() // Resolve any data here
};
mysql.getIamConnection.mockReturnValue({
transaction: jest.fn((callback) => callback(mockTrx)),
});
You need to mock the transaction so that it executes your callback with a dummy trx. To do this, you need to make sure that all the functions inside the trx object return a reference back to it or a promise so that you can chain it appropriately.
Instead of mocking knex implementation, I've written knex-mock-client which allows you to mimic real db with an easy API.
Change your mock implementation with
import { handler } from "../src";
import { getTracker } from "knex-mock-client";
const mocks = require("./mocks");
jest.mock("#org/aws-connection", () => {
const knex = require("knex");
const { MockClient } = require("knex-mock-client");
return {
mysql: {
getIamConnection: () => knex({ client: MockClient }),
},
};
});
describe("handler", () => {
test("test handler", async () => {
const tracker = getTracker();
tracker.on.insert("my_table").responseOnce([23]); // setup's a mock response when inserting into my_table
const response = await handler(mocks.eventSqs);
expect(response.statusCode).toEqual(200);
});
});
I'm New to unit test and trying to test my controller method.my project architecture design is as follow
Controller->Service->Model.
My test scenarios :
Pass correct parameters to controller method and test success response
Pass Invalid parameters to controller method and test error response
When i going to test scenario 1 ,according to my understanding i want to mock my programService and it return values.I have write test as follow and got errors.
I would really appreciate some one can fix this
ProgramsController.js
const ProgramService = require('../../services/program/programService');
class ProgramsController {
constructor() {
this.programService = new ProgramService();
}
async subscribe(req, res) {
try {
const { userId, uuid, msisdn, body: { programId } } = req;
const data = { userId, programId, msisdn, uuid }
const subscribe = await this.programService.subscribeUser(data);
res.json({
status: true,
message: 'Success',
friendly_message: constant.MSG.SUBSCRIPTION,
data: subscribe
})
} catch (error) {
res.status(500)
.json({
status: false,
message: 'Fail',
friendly_message: constant.MSG.SUBSCRIPTION_FAIL
})
}
}
}
ProgramService.js
class ProgramService {
constructor() {
this.subscriber = new Subscriber();
this.subsciberProgram = new SubsciberProgram()
}
async subscribeUser(data) {
try {
const { msisdn, userId, programId, uuid } = data;
...
return subscribedData;
} catch (error) {
throw error;
}
}
}
module.exports = ProgramService;
test.spec.js
const ProgramsService = require('../src/services/program/programService')
const ProgramsController = require('../src/controllers/programs/programsController')
const programController = new ProgramsController()
const programsService = new ProgramsService()
beforeAll(() => {
db.sequelize.sync({ force: true }).then(() => { });
});
const mockRequest = (userId, uuid, msisdn, body) => ({
userId,
uuid,
msisdn,
body,
});
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
const serviceRecord = { userId: 1, programId: 1, msisdn: '56768382967', uuid: '46651a19-3ef1-4149-818e-9bd8a5f359ef' };
const fakeServiceReturn = { program_id: 1, amount: 5, no_of_questions: 10 }
describe('Subscribe', () => {
test('should return 200', async () => {
const req = mockRequest(
1,
'56768382967',
'46651a19-3ef1-4149-818e-9bd8a5f359ef',
{ 'programId': 1 }
);
const res = mockResponse();
const spy = jest.spyOn(programsService, 'subscribeUser').mockImplementation(() => serviceRecord);
await programController.subscribe(req, res);
expect(programsService.subscribeUser()).toHaveBeenCalledWith(fakeServiceReturn);
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
status: true,
message: 'Success',
friendly_message: 'successfull get data',
data : { program_id: 1, amount: 5, no_of_questions: 10 }
});
spy.mockRestore();
});
});
how can i mock programService.subscribeUser and test success response?
This mock should return a promise:
jest.spyOn(programsService, 'subscribeUser').mockImplementation(() => Promise.resolve(serviceRecord));
So I have written a custom polling hook which uses useContext and useLazyQuery hooks. I want to write a unit test for this, which should cover its returned values state and side effect.
So far I have managed to do this much but I'm not so sure how to proceed ahead. Any tips?
export const useUploadActivityPolling = (
teId: TeIdType
): UploadActivityPollingResult => {
const { dispatch, uploadActivityId }: StoreContextType = useAppContext();
const [fetchActivityStatus, { error: UploadActivityError, data: UploadActivityData, stopPolling }] = useLazyQuery(
GET_UPLOAD_ACTIVITY,
{
pollInterval: 3000,
fetchPolicy: 'network-only',
variables: { teId, activityId: uploadActivityId },
}
);
useEffect(() => {
if (UploadActivityData) {
setUploadActivityId(
UploadActivityData.getUploadActivityStatus.activity_id,
dispatch
);
updateActivityStateAction(UploadActivityData.getExcelUploadActivityStatus.status, dispatch);
}
}, [UploadActivityData]);
return { fetchActivityStatus, stopPolling, UploadActivityError };
};
import React from 'react';
import { mount } from 'enzyme';
const TestCustomHook = ({ callback }) => {
callback();
return null;
};
export const testCustomHook = callback => {
mount(<TestCustomHook callback={callback} />);
};
describe('useUploadActivityPolling', () => {
let pollingResult;
const teId = 'some id';
beforeEach(() => {
testCustomHook(() => {
pollingResult = useUploadActivityPolling(teId);
});
});
test('should have an fetchActivityStatus function', () => {
expect(pollingResult.fetchActivityStatus).toBeInstanceOf(Function);
});
});
I want to make a stub of the getDbmodel method.
It is defined in basemodel.js which is
import Sequelize from 'sequelize';
import db from '../../config/database';
let instance = null;
class Database {
static getDbmodel = () => {
if (instance != null) {
return instance;
}
instance = new Sequelize(db.default.database, db.default.username, db.default.password, {
host: db.default.host,
dialect: db.default.dialect,
define: {
timestamps: false, // true by default
},
pool: {
max: db.default.maxConnections,
min: db.default.minConnections,
idle: db.default.idleTimeoutMillis,
timezone: '+00:00',
},
});
console.log('inside basemodel');
return instance;
}
}
export { Database as default };
// ========================================================
// src/Libraries/ABTest.js
import ABTestStorage from '../Storage/ABTest';
class ABTestLib {
getABTestById = id => ABTestStorage.getABTestById(id);
getABTests = () => ABTestStorage.getABTests().then()
.catch(err => console.log(err))
save = abtestObj => ABTestStorage.saveABTest(abtestObj).then()
.catch(err => console.log(err))
put = abtestObj => ABTestStorage.updateABTest(abtestObj)
.then()
.catch(err => console.log(err))
delete = id => ABTestStorage.destroyABTestById(id).then()
.catch(err => console.log(err))
}
export default ABTestLib;
abtest.js imports ab test model which imports basemodel.
This is my test file:
import chai from 'chai';
const SequelizeMock = require('sequelize-mock');
var proxyquire = require('proxyquire');
describe('Libraries/ABTest.js', () => {
before(function () {
let instance = null;
class Database {
static getDbmodel = () => {
if (instance != null) {
return instance;
}
instance = new SequelizeMock();
console.log(instance);
return instance;
}
}
});
var stubs = {
'../..src/Libraries/BaseModel': {
getDbmodel: function () {
console.log('3444e4');
if (instance != null) {
console.log('1111');
return instance;
}
instance = new SequelizeMock();
console.log(instance);
return instance;
}
}
};
const ABTests = proxyquire('../../src/Libraries/ABTest', stubs);
console.log(ABTests);
const abtest = new ABTests.default();
it('get(1): should return response for a abtest', () => {
abtest.getABTestById(1).then((res) => {
chai.expect(res).to.be.an('object');
});
});
it('getAll() : should return response and status 200 for all abtests', () => {
abtest.getABTests().then((res) => {
chai.expect(res).to.be.an('object');
});
});
it('save() : should return response and status 200 for a abtest', () => {
abtest.save({ id: '500', name: 'home', code: 'home', id_organization: '1', id_platform: '1', }).then((res) => {
chai.expect(res).to.be.an('object');
});
});
it('put() : should return response and status 200 for a abtest', () => {
abtest.put({ id: '2', }).then((res) => {
chai.expect(res).to.be.an('object');
});
});
it('delete() : should return response and status 200 for a abtest', () => {
abtest.delete({ id: '111', }).then((res) => {
chai.expect(res).to.be.an('object');
});
});
});
Why does it always go into basemodels gedbmodel and not the mocked one?
I want to mock the getdbmodel method.
Try using noCallThru function in proxyquire:
proxyquire.noCallThru().load('../../src/Libraries/ABTest', stubs).default