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
Related
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
I'm trying to create a simple class that wraps the Amazon SDK for dynamodb. When testing, I get the error:
TypeError: this.dynamodb.batchGetItem is not a function
Here is my testcase: test.js
import DynamoDBContentAccess from "../../../src/api/dynamodb_content_access";
const AWS = require("aws-sdk");
describe("services", () => {
it("returns a list of services", async () => {
let serviceInterface = new DynamoDBContentAccess();
const params = {
bearerToken: "fake bearer token",
filter: {},
skip: 10,
limit: 10,
};
const mockResponse = {
Responses: {
Services: [
{
ServiceId: {
S: "example1",
},
},
],
},
}
const batchGetItemPromise = jest.fn().mockReturnValue({
promise: jest.fn().mockResolvedValue(mockResponse),
});
serviceInterface.dynamodb = jest.fn().mockImplementation(() => { return {
batchGetItem: batchGetItemPromise,
}});
const result = await serviceInterface.findServices(params);
expect(result).toEqual(mockResponse);
});
});
Here is the class under test: dynamodb_content_access.js
import ContentAccess from "./content_access"
const AWS = require("aws-sdk");
class DynamoDBContentAccess {
constructor() {
this.dynamodb = new AWS.DynamoDB();
}
async findServices (bearerToken, filter, skip, limit) {
const params = {
TableName: 'Services',
};
const results = await this.dynamodb.batchGetItem(params).promise();
return results
}
}
export default DynamoDBContentAccess;
Is my mock at the wrong level? What am I missing?
You can use jest.spyOn(object, methodName) to mock AWS.DynamoDB class and its instance.
Use mockFn.mockReturnThis() to mock method chain call.
E.g.
dynamodb_content_access.js:
const AWS = require('aws-sdk');
class DynamoDBContentAccess {
constructor() {
this.dynamodb = new AWS.DynamoDB();
}
async findServices(bearerToken, filter, skip, limit) {
const params = {
TableName: 'Services',
};
const results = await this.dynamodb.batchGetItem(params).promise();
return results;
}
}
export default DynamoDBContentAccess;
dynamodb_content_access.test.js:
import DynamoDBContentAccess from './dynamodb_content_access';
const AWS = require('aws-sdk');
describe('services', () => {
it('returns a list of services', async () => {
const params = {
bearerToken: 'fake bearer token',
filter: {},
skip: 10,
limit: 10,
};
const mockResponse = {
Responses: {
Services: [
{
ServiceId: {
S: 'example1',
},
},
],
},
};
const dynamodb = {
batchGetItem: jest.fn().mockReturnThis(),
promise: jest.fn().mockResolvedValueOnce(mockResponse),
};
const DynamoDBSpy = jest.spyOn(AWS, 'DynamoDB').mockImplementation(() => dynamodb);
let serviceInterface = new DynamoDBContentAccess();
const result = await serviceInterface.findServices(params);
expect(result).toEqual(mockResponse);
expect(DynamoDBSpy).toBeCalledTimes(1);
expect(dynamodb.batchGetItem).toBeCalledWith({ TableName: 'Services' });
expect(dynamodb.promise).toBeCalledTimes(1);
});
});
unit test result:
PASS examples/66858621/dynamodb_content_access.test.js (7.959 s)
services
✓ returns a list of services (3 ms)
----------------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
dynamodb_content_access.js | 100 | 100 | 100 | 100 |
----------------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.846 s, estimated 10 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
}
};
}
}
};
});
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