Mocking the same value between different tests - jestjs

I have a file called constants with a series of string values that get imported into the file where init() is defined. I want to setup my test so that the string values are different between each run. Right now I can only seem to have them be the same for all test runs.
jest.mock('../src/constants', () => ({
build: 'dist',
action: {
pusher: {
name: 'montezuma',
email: 'best#cat'
},
gitHubToken: 'exists'
}
}));
describe('git', () => {
describe('init', () => {
it('should execute three commands', async () => {
await init();
expect(execute).toBeCalledTimes(3);
});
it('should fail if the deployment folder begins with /', async () => {
// I want the values of constants to be different in here.
});
});
});
How can I set it up so action and build have different values in each test run? I've tried moving the jest.mock call into each it statement but that doesn't seem to work. Is there a better pattern for this?

You can use jest.spyOn(object, methodName, accessType?) to mock the return value for each unit test case.
E.g.
contants.ts:
export function get() {
return {
build: 'dist',
action: {
pusher: {
name: 'montezuma',
email: 'best#cat'
},
gitHubToken: 'exists'
}
};
}
index.ts:
import { get } from './constants';
export async function init() {
return get();
}
index.spec.ts:
import { init } from './';
import * as constants from './constants';
describe('git', () => {
describe('init', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('t-1', async () => {
const mConstants = {
build: '/',
action: {
pusher: {
name: 'asd',
email: 'as#cat'
},
gitHubToken: 'ccsa'
}
};
const spy = jest.spyOn(constants, 'get').mockReturnValueOnce(mConstants);
const actualValue = await init();
expect(actualValue).toEqual(mConstants);
expect(spy).toBeCalledTimes(1);
});
it('t-2', async () => {
const mConstants = {
build: 'build',
action: {
pusher: {
name: 'zzz',
email: 'www#cat'
},
gitHubToken: 'xxx'
}
};
const spy = jest.spyOn(constants, 'get').mockReturnValueOnce(mConstants);
const actualValue = await init();
expect(actualValue).toEqual(mConstants);
expect(spy).toBeCalledTimes(1);
});
});
});
Unit test result:
PASS src/stackoverflow/58758771/index.spec.ts (7.685s)
git
init
✓ t-1 (5ms)
✓ t-2 (2ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 8.829s
Update, we can also change the constants object for each unit test case.
Before running the unit test, we should store the original constants. After each test case is finished, we should restore the original constants. I use lodash.cloneDeep method to make a deep clone for constants.
constants.ts:
export const constants = {
build: 'dist',
action: {
pusher: {
name: 'montezuma',
email: 'best#cat'
},
gitHubToken: 'exists'
}
};
index.ts:
import { constants } from './constants';
export async function init() {
return constants;
}
index.spec.ts:
import { init } from './';
import { constants } from './constants';
import _ from 'lodash';
const originalConstants = _.cloneDeep(constants);
describe('git', () => {
afterEach(() => {
_.assignIn(constants, originalConstants);
});
describe('init', () => {
it('t-1', async () => {
Object.assign(constants, {
build: '/',
action: {
...constants.action,
pusher: { ...constants.action.pusher, name: 'aaa', email: 'aaa#cat' },
gitHubToken: 'bbb'
}
});
const actualValue = await init();
expect(actualValue).toEqual({
build: '/',
action: {
pusher: {
name: 'aaa',
email: 'aaa#cat'
},
gitHubToken: 'bbb'
}
});
});
it('should restore original contants', () => {
expect(constants).toEqual({
build: 'dist',
action: {
pusher: {
name: 'montezuma',
email: 'best#cat'
},
gitHubToken: 'exists'
}
});
});
});
});
Unit test result:
PASS src/stackoverflow/58758771/v2/index.spec.ts (10.734s)
git
init
✓ t-1 (7ms)
✓ should restore original contants (1ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 12.509s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58758771

Related

How mock a third-party module in Jest/NestJS?

I've the following code and I need to mock the connection.execute() function as this belongs to the third-party lib snowflake-sdk and is't not part of my unit test:
import * as SnowflakeSDK from 'snowflake-sdk';
import { Injectable } from '#nestjs/common';
#Injectable()
export class SnowflakeClient {
export(bucket: string, filename: string, query: string) {
return new Promise((resolve, reject) => {
const connection = this.getConnection();
const command = `COPY INTO '${bucket}${filename}' FROM (${query})`;
connection.execute({
sqlText: command,
complete: function (err) {
try {
if (err) {
return reject(err);
} else {
return resolve(true);
}
} finally {
connection.destroy(function (err) {
if (err) {
console.error('Unable to disconnect: ' + err.message);
}
});
}
},
});
});
}
getConnection() {
const connection = SnowflakeSDK.createConnection({
account: process.env.SNOWFLAKE_HOST!,
username: process.env.SNOWFLAKE_USERNAME!,
password: process.env.SNOWFLAKE_PASSWORD!,
role: process.env.SNOWFLAKE_ROLE!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE!,
database: process.env.SNOWFLAKE_DATABASE!,
schema: process.env.SNOWFLAKE_SCHEMA!,
});
connection.connect(function (err: Error): void {
if (err) {
throw err;
}
});
return connection;
}
}
So I've created the following unit test:
import { SnowflakeClient } from 'src/snowflake/snowflake-client';
import { Test } from '#nestjs/testing';
describe('SnowflakeClient', () => {
let snowflakeClient: SnowflakeClient;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [SnowflakeClient],
}).compile();
snowflakeClient = moduleRef.get<SnowflakeClient>(SnowflakeClient);
});
describe('export', () => {
it('should export a SQL query', async () => {
const connectionMock = jest.fn().mockImplementation(() => ({
execute: function (bucket: string, filename: string, sql: string) {
console.log(`${bucket}${filename}: ${sql}`);
},
}));
jest.spyOn(snowflakeClient, 'getConnection').mockImplementation(connectionMock);
const bucket = 's3://bucketName';
const filename = '/reports/test.csv';
const sql = 'select * from customers limit 100';
await expect(await snowflakeClient.export(bucket, filename, sql)).resolves.not.toThrow();
}, 10000);
});
});
But the connectionMock.execute isn't being called correctly as I'm getting the following error:
FAIL src/snowflake/snowflake-client.spec.ts (13.518 s)
SnowflakeClient
export
✕ should export a SQL query (10018 ms)
● SnowflakeClient › export › should export a SQL query
thrown: "Exceeded timeout of 10000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
14 |
15 | describe('export', () => {
> 16 | it('should export a SQL query', async () => {
| ^
17 | const connectionMock = jest.fn().mockImplementation(() => ({
18 | execute: function (bucket: string, filename: string, sql: string) {
19 | console.log(`${bucket}${filename}: ${sql}`);
at src/snowflake/snowflake-client.spec.ts:16:5
at src/snowflake/snowflake-client.spec.ts:15:3
at Object.<anonymous> (src/snowflake/snowflake-client.spec.ts:4:1)
I would like to test the SnowflakeClient.export() method but I need to mock the snowflake-sdk module as it isn't part of my code.
Anybody knows what I'm doing wrong?
When using sdk, it's better to pass them through the injection system of NestJs with a custom provider:
//////////////////////////////////
// 1. Provide the sdk in the module.
//////////////////////////////////
const SnowflakeConnectionProvider = {
provide: 'SNOWFLAKE_CONNECTION',
useFactory: () => {
const connection = SnowflakeSDK.createConnection({
account: process.env.SNOWFLAKE_HOST!,
username: process.env.SNOWFLAKE_USERNAME!,
password: process.env.SNOWFLAKE_PASSWORD!,
role: process.env.SNOWFLAKE_ROLE!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE!,
database: process.env.SNOWFLAKE_DATABASE!,
schema: process.env.SNOWFLAKE_SCHEMA!,
});
connection.connect(function (err: Error): void {
if (err) {
throw err;
}
});
return connection;
},
inject: [],
}
#Module({
providers: [
SnowflakeClient,
SnowflakeConnectionProvider,
]
})
export class SnowflakeModule {}
//////////////////////////////////
// 2. Inject the connection into your `SnowflakeClient`
//////////////////////////////////
#Injectable()
export class SnowflakeClient {
constructor(#Inject('SNOWFLAKE_CONNECTION') private readonly connection: SnowflakeConnection) {}
// ...
}
//////////////////////////////////
// 3. Mock the dependency in tests
//////////////////////////////////
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
{
provide: 'SNOWFLAKE_CONNECTION',
useValue: { /* Mock */},
},
SnowflakeClient,
],
}).compile();
snowflakeClient = moduleRef.get<SnowflakeClient>(SnowflakeClient);
});
You will have a much better control over the tests.
So I got this fixed doing this (but I'm not sure it's the best way):
const connectionMock = jest.createMockFromModule<Connection>('snowflake-sdk');
connectionMock.execute = jest.fn().mockImplementation(() => {
return Promise.resolve(null);
});
jest.spyOn(snowflakeClient, 'getConnection').mockImplementation(() => {
return connectionMock;
});
expect(snowflakeClient.export(bucket, filename, sql)).resolves.not.toThrow();
Let me know your thoughts :)

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

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

Unit test gatsby useStaticQuery custom hook

I'm trying to test a custom hook which just returns graphql data in gatsby.
Here is what I have so far but it's giving me an error.
hook useMyData
import { useStaticQuery, graphql } from 'gatsby';
export default () => {
const {
content: { data },
} = useStaticQuery(graphql`
query myQuery {
content {
data {
views: 10
}
}
}
`);
return data;
};
Jest test
import useMyData from './useMyData';
jest.mock('./useMyData', () => ({
__esModule: true,
default: () => ({
useStaticQuery: () => ({
content: {
data: {
test: 'test',
},
},
}),
}),
}));
test('data is returned', () => {
const data = useMyData();
// console.log('data = ', data);
});
The above does not run the useStaticQuery. Any know how I would test this.
You need to directly mock the useStaticQuery method on gatsby module instead of on ./useMyData. Something along the lines of:
jest.mock('gatsby', () => ({
__esModule: true,
useStaticQuery: () => ({
content: {
data: {
test: 'test',
},
},
}),
}));

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