I'm using the #google-cloud/firestore package in my NodeJS project and I'm trying to set up some unit tests using Jest. I'm currently mocking the package like this (using a manual mock in the __mocks__ folder):
const mockFirestore = {
collection: jest.fn(() => mockCollection),
};
module.exports = {
Firestore: jest.fn(() => mockFirestore),
};
This works perfectly for mocking queries, but I'm running into an issue when I try to call a function that uses a Firestore FieldValue like this:
import {Firestore} from '#google-cloud/firestore';
const firestore = new Firestore();
const main = async () => {
await firestore
.collection('foo')
.doc('bar')
.set({
foo: 'baz',
updated: Firestore.FieldValue.serverTimestamp()
}, {merge: true});
}
main();
Any ideas or best-practices on how to mock this?
Thanks!
Just assign mocked static property FieldValue and method to Firestore class. Use mock.mockReturnValueOnce() method to mock the return value of .serverTimestamp() method for different test case.
__mocks__/#google-cloud/firestore/index.js:
const mockFirestore = {
collection: jest.fn().mockReturnThis(),
doc: jest.fn().mockReturnThis(),
set: jest.fn(),
};
const MockFirestore = jest.fn(() => mockFirestore);
MockFirestore.FieldValue = {
serverTimestamp: jest.fn(),
};
module.exports = {
Firestore: MockFirestore,
};
main.js:
import { Firestore } from '#google-cloud/firestore';
const firestore = new Firestore();
const main = async () => {
await firestore.collection('foo').doc('bar').set(
{
foo: 'baz',
updated: Firestore.FieldValue.serverTimestamp(),
},
{ merge: true }
);
};
export { main };
main.test.js:
import { main } from './main';
import { Firestore } from '#google-cloud/firestore';
jest.mock('#google-cloud/firestore');
describe('67268943', () => {
it('should pass', async () => {
Firestore.FieldValue.serverTimestamp.mockReturnValueOnce(2021);
expect(Firestore).toBeCalledTimes(1);
await main();
const firestore = new Firestore();
expect(firestore.collection).toBeCalledWith('foo');
expect(firestore.doc).toBeCalledWith('bar');
expect(firestore.set).toBeCalledWith(
{
foo: 'baz',
updated: 2021,
},
{ merge: true }
);
});
});
unit test result:
PASS examples/67268943/main.test.js (10.868 s)
67268943
✓ should pass (4 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
main.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.589 s
Related
I'm currently having some trouble testing my createAsyncThunk function.
Basically the function is:
const myFunc = createAsyncThunk('returns ID', async (nameAndEmail) => {
const response = await axios.post('/backendroute', nameAndEmail);
return response.data.id;
};
So this function will send the name and email to the backend which returns an ID.
My testing is currently:
test('returns ID when myFunc is called', async () => {
const nameAndEmail = {
name: 'John Smith',
email: '123#123.com'
};
const mockThunk = store.dispatch(myFunc(nameAndEmail));
expect(mockThunk).toHaveBeenCalledWith(nameAndEmail);
});
The problem is that when I test this, the received value is:
Matcher error: received value must be a mock or spy function
{"abort": [Function abort], "arg": {"email": "123#123.com", "name": "John Smith"}, "requestId": "123456789"}
Could anyone please advise what I'm doing wrong?
You should create a store for testing. After dispatching the asynchronous thunk, assert the value of the final state. Use jest.spyOn() to mock axios.post() method and its returned value.
E.g.
thunk.ts:
import { createAsyncThunk } from '#reduxjs/toolkit';
import axios from 'axios';
const myFunc = createAsyncThunk<string, { name: string; email: string }>('returns ID', async (nameAndEmail) => {
const response = await axios.post('/backendroute', nameAndEmail);
return response.data.id;
});
export { myFunc };
thunk.test.ts:
import { myFunc } from './thunk';
import { configureStore } from '#reduxjs/toolkit';
import axios from 'axios';
describe('67087596', () => {
it('should pass', async () => {
const nameAndEmail = {
name: 'John Smith',
email: '123#123.com',
};
const postSpy = jest.spyOn(axios, 'post').mockResolvedValueOnce({ data: { id: '1' } });
const store = configureStore({
reducer: function (state = '', action) {
switch (action.type) {
case 'returns ID/fulfilled':
return action.payload;
default:
return state;
}
},
});
await store.dispatch(myFunc(nameAndEmail));
expect(postSpy).toBeCalledWith('/backendroute', nameAndEmail);
const state = store.getState();
expect(state).toEqual('1');
});
});
unit test result:
PASS examples/67087596/thunk.test.ts (11.099 s)
67087596
✓ should pass (7 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
thunk.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 13.071 s
I'm trying to write a unit test that checks if the .track method of the Analytics is called. For some reason the test keeps failing, although calling the function via http does trigger the call. I'm not sure if I've mocked it wrong, or what the issue could be?
index.ts:
import { Request } from "../types"
import { getSecret } from "../src/secrets"
import Analytics from "analytics-node"
const logger = (req: Request) => {
const analytics = new Analytics(<string>process.env.WRITE_KEY);
return analytics.track({
userId: req.userId
});
}
export default logger
index.test.ts:
jest.mock('analytics-node');
import { Request } from "../types"
import logger from "./index"
import Analytics from "analytics-node"
const mockAnalytics = new Analytics(process.env.WRITE_KEY = 'test');
describe('Logger tests', () => {
it(`Should call analytics.track`, () => {
const request: Request = {
userId: 23
}
return logger(request).then(() => {
expect(mockAnalytics.track).toHaveBeenCalled()
});
});
});
You are using Automatic mock by calling jest.mock('analytics-node').
Calling jest.mock('analytics-node') returns a useful "automatic mock" you can use to spy on calls to the class constructor and all of its methods. It replaces the ES6 class with a mock constructor and replaces all of its methods with mock functions that always return undefined. Method calls are saved in theAutomaticMock.mock.instances[index].methodName.mock.calls.
E.g.
index.ts:
import Analytics from 'analytics-node';
export interface Request {
userId: string | number;
}
const logger = (req: Request) => {
const analytics = new Analytics(<string>process.env.WRITE_KEY);
return analytics.track({
userId: req.userId,
anonymousId: 1,
event: '',
});
};
export default logger;
index.test.ts:
import logger, { Request } from './';
import Analytics from 'analytics-node';
jest.mock('analytics-node');
const mockAnalytics = Analytics as jest.MockedClass<typeof Analytics>;
describe('Logger tests', () => {
afterAll(() => {
jest.resetAllMocks();
});
it(`Should call analytics.track`, () => {
const WRITE_KEY = process.env.WRITE_KEY;
process.env.WRITE_KEY = 'test key';
const request: Request = {
userId: 23,
};
logger(request);
expect(mockAnalytics).toBeCalledWith('test key');
expect(mockAnalytics.mock.instances[0].track).toHaveBeenCalled();
process.env.WRITE_KEY = WRITE_KEY;
});
});
unit test result:
PASS examples/65412302/index.test.ts
Logger tests
✓ Should call analytics.track (4 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.954 s
By using "#segment/analytics-next": "1.49.2"
let analyticsInstance: AnalyticsBrowser | null | undefined;
export const loadAnalytics = (props?: LoadAnalyticsProps): typeof analyticsInstance => {
if ((analyticsInstance && analyticsInstance.instance?.initialized) || !props) {
return analyticsInstance;
}
const { segmentAnalyticsKey, anonymousId, userId } = props;
if (!segmentAnalyticsKey) {
return null;
}
analyticsInstance = AnalyticsBrowser.load({ writeKey: segmentAnalyticsKey });
analyticsInstance.setAnonymousId(anonymousId);
analyticsInstance.identify(userId);
return analyticsInstance;
};
mock
const setAnonymousIdMock = jest.fn();
const identifyMock = jest.fn();
jest.mock("#segment/analytics-next", () => {
const originalModule = jest.requireActual("#segment/analytics-next");
return {
__esModule: true,
...originalModule,
AnalyticsBrowser: {
load: () => {
return {
setAnonymousId: setAnonymousIdMock,
identify: identifyMock,
instance: {
initialized: true
}
};
}
}
};
});
This is a part of my app:
import validationSchema from "./../_validation/report";
const reportModel = require("../models/report");
ctrl.addReport = async (req, res) => {
const { body } = req;
try {
const validatedData = await validationSchema.validate(body);
const report = await reportModel(req.dbConnection).addReport(validatedData);
// something more
} catch (err) {
throw boom.badRequest(err);
}
};
module.exports = ctrl;
I would like to write some unit test but I want to test that addReport from reportModel has been called with the correct argument.
To mock reportModel I use jest.mock like this:
const reportModel = require("../models/report");
jest.mock('../models/report', () => {
return () => {
return {
addReport: jest.fn(() => {
return true
})
}
}
});
// do some tests and call function which call addReport from reportModel
expect(reportModel().addReport).toHaveBeenCalledTimes(1);
I've got 0. How can I check that addReport from reportModel has been called?
You didn't mock ./models/report module correctly. Here is the solution:
ctrl.js:
import validationSchema from './_validation/report';
import reportModel from './models/report';
const ctrl = {};
ctrl.addReport = async (req, res) => {
const { body } = req;
const validatedData = await validationSchema.validate(body);
const report = await reportModel(req.dbConnection).addReport(validatedData);
};
module.exports = ctrl;
./models/report.js:
function reportModel(connection) {
return {
async addReport(data) {
console.log('addReport');
},
};
}
export default reportModel;
./_validation/report.js:
const validationSchema = {
validate(body) {
return body;
},
};
export default validationSchema;
ctrl.test.js:
import reportModel from './models/report';
const ctrl = require('./ctrl');
jest.mock('./models/report', () => {
const mReportModel = {
addReport: jest.fn(() => true),
};
return jest.fn(() => mReportModel);
});
describe('59431651', () => {
it('should pass', async () => {
const mReq = { body: 'mocked data', dbConnection: {} };
const mRes = {};
await ctrl.addReport(mReq, mRes);
expect(reportModel).toBeCalledWith(mReq.dbConnection);
expect(reportModel(mReq.dbConnection).addReport).toHaveBeenCalledTimes(1);
});
});
Unit test result with coverage report:
PASS src/stackoverflow/59431651/ctrl.test.js
59431651
✓ should pass (7ms)
----------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
59431651 | 100 | 100 | 100 | 100 | |
ctrl.js | 100 | 100 | 100 | 100 | |
59431651/_validation | 100 | 100 | 100 | 100 | |
report.js | 100 | 100 | 100 | 100 | |
----------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.682s, estimated 11s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59431651
I'm trying to catch errors in my async functions and test those functions using jest.
import Db from './lib/db'
const db = new Db()
export async function example (id) {
await db.connect().catch(err => console.error(err))
const Data = db.connection.collection('data')
return Data.updateOne(
{ id },
{ $set: { anything: 'else' } }
).catch(err => console.error(err))
}
But the test returns me the error:
TypeError: db.connect(...).catch is not a function
So what am I doing wrong? The testing or the catching?
test('should call mongoDB updateOne() method', async () => {
// SETUP
let Data = db.connection.collection('data')
Data.updateOne = jest.fn()
// EXECUTE
expect.assertions(1)
await example({
id: 'id'
}).then((res) => {
// VERIFY
expect(Data.updateOne).toHaveBeenCalled()
})
})
Here is the solution:
Db.ts:
import console = require('console');
export class Db {
public connection = {
collection(model: string) {
return {
async updateOne(...args) {
console.log('update one');
}
};
}
};
public async connect() {
console.log('connect db');
}
}
example.ts:
import { Db } from './Db';
const db = new Db();
export async function example(id) {
await db.connect().catch(err => console.error(err));
const Data = db.connection.collection('data');
return Data.updateOne({ id }, { $set: { anything: 'else' } }).catch(err => console.error(err));
}
example.spec.ts:
import { example } from './example';
import { Db } from './Db';
jest.mock('./Db.ts', () => {
const collectionMocked = {
updateOne: jest.fn()
};
const connectionMocked = {
collection: jest.fn(() => collectionMocked)
};
const DbMocked = {
connect: jest.fn(),
connection: connectionMocked
};
return {
Db: jest.fn(() => DbMocked)
};
});
const db = new Db();
describe('example', () => {
test('should call mongoDB updateOne() method', async () => {
const Data = db.connection.collection('data');
(db.connect as jest.MockedFunction<any>).mockResolvedValueOnce({});
(Data.updateOne as jest.MockedFunction<any>).mockResolvedValueOnce('mocked value');
const actualValue = await example('1');
expect(actualValue).toBe('mocked value');
expect(Data.updateOne).toHaveBeenCalledWith({ id: '1' }, { $set: { anything: 'else' } });
});
});
Unit test result with coverage report:
PASS src/stackoverflow/57515192/example.spec.ts
example
✓ should call mongoDB updateOne() method (6ms)
------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------|----------|----------|----------|----------|-------------------|
All files | 75 | 100 | 33.33 | 100 | |
example.ts | 75 | 100 | 33.33 | 100 | |
------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.336s, estimated 6s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57515192
i have little experience with jest as i am trying to change variable in my function as we expect to have error thrown if the function's inner variable(baseUrl) changed to null, my function :
buildUrl.js:
export function buildUrl(contentId, data, options = {}) {
let baseUrl = config.has('imgBaseUrl') && config.get('imgBaseUrl');
if (!baseUrl) {
throw new Error('some error');
}
}
i need to mock the baseUrl to value of null for example and test it
buildUrl.test.js
import {buildUrl} from "./buildURL";
....
it('throw an error when "baseUrl" is not configured', () => {
let mockBaseUrl = {baseUrl: null};
jest.mock('./buildURL', ()=> mockBaseUrl);
// jest.mock('../../../config/test', ()=>mockImgBaseUrl); // or mock the config to be used by the function?
expect(()=> buildUrl('1.44444', data[0], defaultOptions)).toThrow(
'some error'
);
});
another approach using jest.fn() didnt work as expected , maybe i am missing something here...
Here is the unit test solution:
baseUrl.js:
import config from './config';
export function buildUrl(contentId, data, options = {}) {
let baseUrl = config.has('imgBaseUrl') && config.get('imgBaseUrl');
if (!baseUrl) {
throw new Error('some error');
}
console.log(baseUrl);
}
config.js:
export default {
config: {},
has(key) {
return !!config[key];
},
get(key) {
return config[key];
}
};
baseUrl.spec.js:
import { buildUrl } from './buildUrl';
import config from './config';
describe('buildUrl', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('should throw error when baseUrl is null', () => {
const defaultOptions = {};
const data = ['fake data'];
const hasSpy = jest.spyOn(config, 'has').mockReturnValueOnce(true);
const getSpy = jest.spyOn(config, 'get').mockReturnValueOnce(null);
expect(() => buildUrl('1.44444', data[0], defaultOptions)).toThrowError('some error');
expect(hasSpy).toBeCalledWith('imgBaseUrl');
expect(getSpy).toBeCalledWith('imgBaseUrl');
});
it('should log baseUrl', () => {
const defaultOptions = {};
const data = ['fake data'];
const hasSpy = jest.spyOn(config, 'has').mockReturnValueOnce(true);
const getSpy = jest.spyOn(config, 'get').mockReturnValueOnce('https://github.com/mrdulin');
const logSpy = jest.spyOn(console, 'log');
buildUrl('1.44444', data[0], defaultOptions);
expect(hasSpy).toBeCalledWith('imgBaseUrl');
expect(getSpy).toBeCalledWith('imgBaseUrl');
expect(logSpy).toBeCalledWith('https://github.com/mrdulin');
});
});
Unit test result:
PASS src/stackoverflow/48006588/buildUrl.spec.js (8.595s)
buildUrl
✓ should throw error when baseUrl is null (9ms)
✓ should log baseUrl (7ms)
console.log node_modules/jest-mock/build/index.js:860
https://github.com/mrdulin
-------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------|----------|----------|----------|----------|-------------------|
All files | 80 | 83.33 | 33.33 | 77.78 | |
buildUrl.js | 100 | 83.33 | 100 | 100 | 3 |
config.js | 33.33 | 100 | 0 | 33.33 | 4,7 |
-------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 9.768s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/48006588
I think you can do this:
jest.mock('./buildURL', () => {buildUrl: jest.fn()})
buildUrl.mockImplementation(() => {baseUrl: null})
See Jest docs