How to change the MOCK implementation of a service per unit test - jestjs

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

Related

Jest - resetting value in module and reloading import

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

Spy function with params by Sinon.js

I'm trying to write some unit tests of code that uses typeorm without hitting the DB.
And I'm using sinon for spy/stub/mock.
This is my function.
async updateDoingToFailedWithLock(queryRunner: QueryRunner): Promise<void> {
await queryRunner.manager
.getRepository(Report)
.createQueryBuilder("report")
.useTransaction(true)
.setLock("pessimistic_write")
.update(Report)
.set({ status: ReportStatus.FAILED })
.where(`(status = "doing")`)
.execute();
}
I already wrote a fake test to make sure execute() is called by using spy function.
But I want to test the params of these functions createQueryBuilder..., the sure the params are correct.
I took a look at sinon document and it seems like sinon support test params by this API: spy().withArgs(arg1, arg2...).
But I'm not sure how to spy my function correctly.
describe("updateDoingToFailedWithLock()", (): void => {
let sandbox: Sinon.SinonSandbox;
beforeEach(() => (sandbox = Sinon.createSandbox()));
afterEach(() => sandbox.restore);
it("should be success", async (): Promise<void> => {
const fakeManager = {
getRepository: () => {
return fakeManager;
},
createQueryBuilder: () => {
return fakeManager;
},
useTransaction: () => {
return fakeManager;
},
setLock: () => {
return fakeManager;
},
update: () => {
return fakeManager;
},
set: () => {
return fakeManager;
},
where: () => {
return fakeManager;
},
execute: () => {},
};
const fakeQueryRunner = {
manager: fakeManager,
};
const connection = new typeorm.Connection({ type: "mysql" });
const reportService = new ReportService();
sandbox.stub(connection, "createQueryRunner").callsFake((): any => {
return fakeQueryRunner;
});
const queryRunner = connection.createQueryRunner();
const spy = sandbox.spy(fakeManager, "execute");
reportService.updateDoingToFailedWithLock(queryRunner);
expect(spy.calledOnce).be.true;
});
});
Any help is welcome. Thanks in advance!
I saw your code and there's something can be improved:
Use returnsThis() to replace return fakeManager
Don't forget await when calling updateDoingToFailedWithLock
describe("updateDoingToFailedWithLock()", (): void => {
let sandbox: sinon.SinonSandbox;
beforeEach(() => (sandbox = sinon.createSandbox()));
afterEach(() => sandbox.restore);
it("should be success", async (): Promise<void> => {
// using returnsThis()
const fakeManager = {
getRepository: sandbox.stub().returnsThis(),
createQueryBuilder: sandbox.stub().returnsThis(),
useTransaction: sandbox.stub().returnsThis(),
setLock: sandbox.stub().returnsThis(),
update: sandbox.stub().returnsThis(),
set: sandbox.stub().returnsThis(),
where: sandbox.stub().returnsThis(),
execute: sandbox.stub().returnsThis(),
}
const fakeQueryRunner = {
manager: fakeManager,
};
const reportService = new ReportService();
// having await here is important
await reportService.updateDoingToFailedWithLock(fakeQueryRunner);
expect(fakeManager.execute.calledOnce).to.be.true;
expect(fakeManager.createQueryBuilder.calledWith('report')).to.be.true;
});
});
Hope it helps

Unit test for customPollingHook which uses apollo useLazyQuery

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

How to mock chained function calls using jest?

I am testing the following service:
#Injectable()
export class TripService {
private readonly logger = new Logger('TripService');
constructor(
#InjectRepository(TripEntity)
private tripRepository: Repository<TripEntity>
) {}
public async showTrip(clientId: string, tripId: string): Promise<Partial<TripEntity>> {
const trip = await this.tripRepository
.createQueryBuilder('trips')
.innerJoinAndSelect('trips.driver', 'driver', 'driver.clientId = :clientId', { clientId })
.where({ id: tripId })
.select([
'trips.id',
'trips.distance',
'trips.sourceAddress',
'trips.destinationAddress',
'trips.startTime',
'trips.endTime',
'trips.createdAt'
])
.getOne();
if (!trip) {
throw new HttpException('Trip not found', HttpStatus.NOT_FOUND);
}
return trip;
}
}
My repository mock:
export const repositoryMockFactory: () => MockType<Repository<any>> = jest.fn(() => ({
findOne: jest.fn(entity => entity),
findAndCount: jest.fn(entity => entity),
create: jest.fn(entity => entity),
save: jest.fn(entity => entity),
update: jest.fn(entity => entity),
delete: jest.fn(entity => entity),
createQueryBuilder: jest.fn(() => ({
delete: jest.fn().mockReturnThis(),
innerJoinAndSelect: jest.fn().mockReturnThis(),
innerJoin: jest.fn().mockReturnThis(),
from: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
execute: jest.fn().mockReturnThis(),
getOne: jest.fn().mockReturnThis(),
})),
}));
My tripService.spec.ts:
import { Test, TestingModule } from '#nestjs/testing';
import { TripService } from './trip.service';
import { MockType } from '../mock/mock.type';
import { Repository } from 'typeorm';
import { TripEntity } from './trip.entity';
import { getRepositoryToken } from '#nestjs/typeorm';
import { repositoryMockFactory } from '../mock/repositoryMock.factory';
import { DriverEntity } from '../driver/driver.entity';
import { plainToClass } from 'class-transformer';
describe('TripService', () => {
let service: TripService;
let tripRepositoryMock: MockType<Repository<TripEntity>>;
let driverRepositoryMock: MockType<Repository<DriverEntity>>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
TripService,
{ provide: getRepositoryToken(DriverEntity), useFactory: repositoryMockFactory },
{ provide: getRepositoryToken(TripEntity), useFactory: repositoryMockFactory },
],
}).compile();
service = module.get<TripService>(TripService);
driverRepositoryMock = module.get(getRepositoryToken(DriverEntity));
tripRepositoryMock = module.get(getRepositoryToken(TripEntity));
});
it('should be defined', () => {
expect(service).toBeDefined();
expect(driverRepositoryMock).toBeDefined();
expect(tripRepositoryMock).toBeDefined();
});
describe('TripService.showTrip()', () => {
const trip: TripEntity = plainToClass(TripEntity, {
id: 'one',
distance: 123,
sourceAddress: 'one',
destinationAddress: 'one',
startTime: 'one',
endTime: 'one',
createdAt: 'one',
});
it('should show the trip is it exists', async () => {
tripRepositoryMock.createQueryBuilder.mockReturnValue(trip);
await expect(service.showTrip('one', 'one')).resolves.toEqual(trip);
});
});
});
I want to mock the call to the tripRepository.createQueryBuilder().innerJoinAndSelect().where().select().getOne();
First question, should I mock the chained calls here because I assume that it should already be tested in Typeorm.
Second, if I want to mock the parameters passed to each chained call and finally also mock the return value, how can I go about it?
I had a similar need and solved using the following approach.
This is the code I was trying to test. Pay attention to the createQueryBuilder and all the nested methods I called.
const reactions = await this.reactionEntity
.createQueryBuilder(TABLE_REACTIONS)
.select('reaction')
.addSelect('COUNT(1) as count')
.groupBy('content_id, source, reaction')
.where(`content_id = :contentId AND source = :source`, {
contentId,
source,
})
.getRawMany<GetContentReactionsResult>();
return reactions;
Now, take a look at the test I wrote that simulates the chained calls of the above methods.
it('should return the reactions that match the supplied parameters', async () => {
const PARAMS = { contentId: '1', source: 'anything' };
const FILTERED_REACTIONS = REACTIONS.filter(
r => r.contentId === PARAMS.contentId && r.source === PARAMS.source,
);
// Pay attention to this part. Here I created a createQueryBuilder
// const with all methods I call in the code above. Notice that I return
// the same `createQueryBuilder` in all the properties/methods it has
// except in the last one that is the one that return the data
// I want to check.
const createQueryBuilder: any = {
select: () => createQueryBuilder,
addSelect: () => createQueryBuilder,
groupBy: () => createQueryBuilder,
where: () => createQueryBuilder,
getRawMany: () => FILTERED_REACTIONS,
};
jest
.spyOn(reactionEntity, 'createQueryBuilder')
.mockImplementation(() => createQueryBuilder);
await expect(query.getContentReactions(PARAMS)).resolves.toEqual(
FILTERED_REACTIONS,
);
});
Guilherme's answer is totally right. I just wanted to offer a modified approach that might apply to more test cases, and in TypeScript. Instead of defining your chained calls as (), you can use a jest.fn, allowing you to make more assertions. e.g.,
/* eslint-disable #typescript-eslint/no-explicit-any */
const createQueryBuilder: any = {
select: jest.fn().mockImplementation(() => {
return createQueryBuilder
}),
addSelect: jest.fn().mockImplementation(() => {
return createQueryBuilder
}),
groupBy: jest.fn().mockImplementation(() => {
return createQueryBuilder
}),
where: jest.fn().mockImplementation(() => {
return createQueryBuilder
}),
getRawMany: jest
.fn()
.mockImplementationOnce(() => {
return FILTERED_REACTIONS
})
.mockImplementationOnce(() => {
return SOMETHING_ELSE
}),
}
/* run your code */
// then you can include an assertion like this:
expect(createQueryBuilder.groupBy).toHaveBeenCalledWith(`some group`)
The solution I found to work in my case was to
create a repository class, add your custom query to the class
#EntityRepository(User)
export class UserRepository extends Repository<User> {
async getStatus(id: string) {
const status = await this.createQueryBuilder()
.select('User.id')
.where('User.id = :id', { id })
.getRawOne();
return {status};
}
}
mock the new repository class using 'jest-mock-extended' and 'jest-when' dependencies. This way you only need to mock the UserRepository and not all it's nested queries.
Now you can define the behaviour of the repository to resolve a predefined object (in my case a Partial object).
// some file where I need to call getStatus() in a test
const userRepoMock = mock<UserRepository>()
// lines omitted
const user = {
status: open,
};
when(userRepoMock.getStatus).mockResolvedValue(user as User);
// assert status

Jest reset mock of a node module

I'm working on the Google Cloud Functions tests.
The files are these:
index.ts which only exports the functions which are also imported there.
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'contactSupportByMail') {
exports.contactSupportByMail = require('./contactSupportByMail');
}
contactSupportByMail.ts the function to test.
And the test:
describe('Cloud Functions', (): void => {
let myFunctions;
let adminInitStub;
beforeAll((): void => {
// [START stubAdminInit]
// If index.js calls admin.initializeApp at the top of the file,
// we need to stub it out before requiring index.js. This is because the
// functions will be executed as a part of the require process.
// Here we stub admin.initializeApp to be a dummy function that doesn't do anything.
adminInitStub = sinon.stub(admin, 'initializeApp');
testEnv.mockConfig({
sendgrid: {
key: 'apiKey',
},
brand: {
support_email: 'supportEmail',
},
});
// [END stubAdminInit]
});
afterAll((): void => {
// Restore admin.initializeApp() to its original method.
adminInitStub.restore();
// Do other cleanup tasks.
process.env.FUNCTION_NAME = '';
myFunctions = undefined;
testEnv.cleanup();
});
describe('contactSupportByMail', (): void => {
// Mocking node_modules library before the require
jest.mock('#sendgrid/mail', (): { [key: string]: any } => ({
setApiKey: (): void => { },
send: (): Promise<any> => Promise.resolve('ok'),
}));
// Setting up cloud function name
process.env.FUNCTION_NAME = 'contactSupportByMail';
// Importing the index file
myFunctions = require('../src/index');
const wrapped = testEnv.wrap(myFunctions.contactSupportByMail);
it('it should export contactSupportByMail', (): void => {
const cFunction = require('../src/contactSupportByMail');
assert.isObject(myFunctions);
assert.include(myFunctions, { contactSupportByMail: cFunction });
});
it('should fully work', async (): Promise<void> => {
const onCallObjects: [any, ContextOptions] = [
{ mailBody: 'mailBody', to: 'toEmail' },
{ auth: { token: { email: 'userEmail' } } },
];
return assert.deepEqual(await wrapped(...onCallObjects), { ok: true });
});
it('not auth', async (): Promise<void> => {
await expect(wrapped(undefined)).rejects.toThrow('The function must be called while authenticated.');
});
it('sendgrid error', async (): Promise<void> => {
// Mocking node_modules library before the require
jest.mock('#sendgrid/mail', (): { [key: string]: any } => ({
setApiKey: (): void => { },
send: (): Promise<any> => Promise.reject('errorsengrid'),
}));
// Importing the index file
const a = require('../src/index');
const wrapped_2 = testEnv.wrap(a.contactSupportByMail);
const onCallObjects: [any, ContextOptions] = [
{ mailBody: 'mailBody', to: 'toEmail' },
{ auth: { token: { email: 'userEmail' } } },
];
await expect(wrapped_2(...onCallObjects)).rejects.toThrow('errorsengrid');
});
});
});
The problem is provoking the sendgrid error. I don't know how to reset the mock of sendgrid's library which is required inside contactSupportByMail. After mocking it for the first time, it always returns the send function as resolved.
Just a note - if using babel-jest, mock calls are hoisted to the top of the transpiled js... doMock allow you to mock in the before functions of a test.
This is one way to mock a module for some tests within a file - and restore it for the others:
describe("some tests", () => {
let subject;
describe("with mocks", () => {
beforeAll(() => {
jest.isolateModules(() => {
jest.doMock("some-lib", () => ({ someFn: jest.fn() }));
subject = require('./module-that-imports-some-lib');
});
});
// ... tests when some-lib is mocked
});
describe("without mocks - restoring mocked modules", () => {
beforeAll(() => {
jest.isolateModules(() => {
jest.unmock("some-lib");
subject = require('./module-that-imports-some-lib');
});
});
// ... tests when some-lib is NOT mocked
});
});
I finally got the solution:
afterEach((): void => {
jest.resetModules();
});

Resources