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
Related
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 have no idea how to test axios wrapper functions. What should I mock?
I have createApiClient.js file:
import createApiClient from '../createApiClient';
import axios from 'axios';
function createApiClient(config = {}) {
const client = axios.create(config);
client.interceptors.response.use((response) => response.data,
(error) => {
if (error.response) {
throw error.response.data;
} else {
throw new Error('Ошибка во время соединения с сервером! Попробуйте повторить попытку позже.');
}
});
return client;
}
export default createApiClient;
Also I have concrete client.js file created with this function:
import createApiClient from '../createApiClient';
const request = createApiClient({
baseURL: process.env.VUE_APP_AUTH_API_URL,
});
async function logIn(username, password) {
const { token } = await request.post('login/', {
username,
password,
});
return token;
}
// other functions...
export { logIn, register, getUserInfo };
How to test logIn() and other functions in client.js? Especially, I'm wondering about axios.create(), interceptors, etc.
I tried this and some variations:
import createApiClient from '#/api/createApiClient';
import { logIn } from '#/api/auth/client';
const token = 'token';
describe('Тестирование API аутентификации', () => {
test('log in success', async () => {
const request = createApiClient();
request.post = jest.fn(() => Promise.resolve(token));
const response = await logIn('foo', 'qwerty');
response.toBe({ token });
});
});
You could use jest.mock(moduleName, factory, options) to mock ./createApiClient module, createApiClient function and its returned value.
E.g.
client.js:
import createApiClient from './createApiClient';
const request = createApiClient({
baseURL: process.env.VUE_APP_AUTH_API_URL,
});
async function logIn(username, password) {
const { token } = await request.post('login/', {
username,
password,
});
return token;
}
export { logIn };
createApiClient.js:
import axios from 'axios';
function createApiClient(config = {}) {
const client = axios.create(config);
client.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response) {
throw error.response.data;
} else {
throw new Error('Ошибка во время соединения с сервером! Попробуйте повторить попытку позже.');
}
},
);
return client;
}
export default createApiClient;
client.test.js:
import { logIn } from './client';
import createApiClientMock from './createApiClient';
jest.mock('./createApiClient', () => {
const axiosInstance = { post: jest.fn() };
return jest.fn(() => axiosInstance);
});
describe('61713112', () => {
it('should pass', async () => {
const axiosInstanceMock = createApiClientMock();
const mResponse = { token: 'token' };
axiosInstanceMock.post.mockResolvedValueOnce(mResponse);
const actual = await logIn('foo', 'qwerty');
expect(actual).toEqual('token');
expect(axiosInstanceMock.post).toBeCalledWith('login/', { username: 'foo', password: 'qwerty' });
});
});
unit test results with coverage report:
PASS stackoverflow/61713112/client.test.js
61713112
✓ should pass (4ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
client.js | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.582s, estimated 20s
I have problem with testing this function. I don't know how I can test checkAuth when decode is mocked.
import decode from "jwt-decode";
export const checkAuth = () => {
const token = localStorage.getItem("token");
if (!token) {
return false;
}
try {
const { exp } = decode(token);
if (exp < new Date().getTime() / 1000) {
return false;
}
} catch (e) {
console.log(e); // 'Invalid token specified: Cannot read property \'replace\' of undefined'
return false;
}
return true;
};
My test doesn't work. It takes original function.
import { Auth, AuthAdmin, checkAuth, AppContent, AuthApp } from "./Auth";
import LocalStorageMock from "../../../mocks/localStorageMock";
import decode from "jwt-decode";
global.localStorage = new LocalStorageMock();
describe("auth", () => {
localStorage.setItem("token", "fake_token_user");
const token = localStorage.getItem("token");
it("allows the user to login successfully", async () => {
const decode = jest.fn(token => {
return {
exp: new Date().getTime() / 1000 - 1,
iat: 1575751766,
userData: { isAdmin: true, login: "one92tb", userId: 1 }
};
});
//const { exp } = decode(token);
expect(token).toBeDefined();
expect(checkAuth()).toBe(true) // It runs original decode function
});
});
Can someone explain to me how to solve that problem?
You can use jest.mock(moduleName, factory, options) method to mock jwt-decode module.
E.g.
index.js:
import decode from 'jwt-decode';
export const checkAuth = () => {
const token = localStorage.getItem('token');
if (!token) {
return false;
}
try {
const { exp } = decode(token);
if (exp < new Date().getTime() / 1000) {
console.log(123);
return false;
}
} catch (e) {
console.log(e);
return false;
}
return true;
};
index.spec.js:
import decode from 'jwt-decode';
import { checkAuth } from '.';
jest.mock('jwt-decode', () => jest.fn());
const mLocalStorage = {
_storage: {},
getItem: jest.fn((key) => {
return this._storage[key];
}),
setItem: jest.fn((key, value) => {
this._storage[key] = value;
}),
};
global.localStorage = mLocalStorage;
describe('auth', () => {
afterEach(() => {
jest.resetAllMocks();
});
it('allows the user to login successfully', async () => {
localStorage.setItem('token', 'fake_token_user');
const token = localStorage.getItem('token');
decode.mockImplementationOnce((token) => {
return {
exp: new Date().getTime() / 1000 + 1,
iat: 1575751766,
userData: { isAdmin: true, login: 'one92tb', userId: 1 },
};
});
expect(token).toBe('fake_token_user');
expect(checkAuth()).toBe(true);
});
});
Unit test result with coverage report:
PASS src/stackoverflow/59233898/index.spec.js (15.687s)
auth
✓ allows the user to login successfully (11ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 61.54 | 50 | 100 | 61.54 | |
index.js | 61.54 | 50 | 100 | 61.54 | 6,12,13,16,17 |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 17.8s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59233898
I am new to Node.js. I was trying to write a jest unit test cases for AWS lambda function(for node environment). I used a node module called "lambda-tester" to test it. But the problem with "lambda-tester" is, it will hit the actual service and return the data. I don't want to do that. I need to mock the service call.
So, I wanted to go with the plain old way. But, I have issues with mocking it. Can you help me to write basic unit test case for the below lambda ith mocking the function "serviceFunction" ?
const dataService = require('../dataService');
exports.lambdaService = async event => {
let response = await serviceFunction(event.id);
if (response.code == 200) {
return response;
} else {
return {
statusCode: response.code,
body: JSON.stringify({
message: response.message
})
};
}
};
const serviceFunction = async id => {
return await dataService.retrieveData(id);
};
You can use jest.spyOn(object, methodName, accessType?) method to mock dataService.retrieveData method. And, your serviceFunction function only has one statement, so you can test lambdaService function with it together.
E.g.
index.js:
const dataService = require('./dataService');
exports.lambdaService = async event => {
let response = await serviceFunction(event.id);
if (response.code == 200) {
return response;
} else {
return {
statusCode: response.code,
body: JSON.stringify({
message: response.message
})
};
}
};
const serviceFunction = async id => {
return await dataService.retrieveData(id);
};
dataService.js:
module.exports = {
retrieveData: async id => {
return { code: 200, data: 'real data' };
}
};
index.spec.js:
const { lambdaService } = require('.');
const dataService = require('./dataService');
describe('lambdaService', () => {
beforeEach(() => {
jest.restoreAllMocks();
});
test('should return data', async () => {
const mResponse = { code: 200, data: 'mocked data' };
const mEvent = { id: 1 };
const retrieveDataSpy = jest.spyOn(dataService, 'retrieveData').mockResolvedValueOnce(mResponse);
const actualValue = await lambdaService(mEvent);
expect(actualValue).toEqual(mResponse);
expect(retrieveDataSpy).toBeCalledWith(mEvent.id);
});
test('should return error message', async () => {
const mResponse = { code: 500, message: 'Internal server error' };
const mEvent = { id: 1 };
const retrieveDataSpy = jest.spyOn(dataService, 'retrieveData').mockResolvedValueOnce(mResponse);
const actualValue = await lambdaService(mEvent);
expect(actualValue).toEqual({ statusCode: 500, body: JSON.stringify({ message: mResponse.message }) });
expect(retrieveDataSpy).toBeCalledWith(mEvent.id);
});
test('should throw an error', async () => {
const mEvent = { id: 1 };
const retrieveDataSpy = jest.spyOn(dataService, 'retrieveData').mockRejectedValueOnce(new Error('network error'));
await expect(lambdaService(mEvent)).rejects.toThrowError(new Error('network error'));
expect(retrieveDataSpy).toBeCalledWith(mEvent.id);
});
});
Unit test result with coverage report:
PASS src/stackoverflow/58623194/index.spec.js
lambdaService
✓ should return data (6ms)
✓ should return error message (4ms)
✓ should throw an error (2ms)
----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------------|----------|----------|----------|----------|-------------------|
All files | 90 | 100 | 66.67 | 90 | |
dataService.js | 50 | 100 | 0 | 50 | 3 |
index.js | 100 | 100 | 100 | 100 | |
----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 4.619s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58623194