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
Related
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 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
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 am using redux-thunk and not sure if side effects (showAlertError function) are structured properly. Although my jest test setup seems to be fine at first glance, I get an error:
jest.fn() value must be a mock function or spy. Received: undefined`
Is the showAlertError function is at the right place or it should be in the action creator or somewhere else? Also if this is the right place for it then how I can test if it's called.
export const submitTeammateInvitation = (data) => {
const config = {
// config code
};
return async (dispatch) => {
dispatch(submitTeammateInvitationRequest(data));
try {
const response = await fetch(inviteTeammateEndpoint, config);
const jsonResponse = await response.json();
if (!response.ok) {
showErrorAlert(jsonResponse);
dispatch(submitTeammateInvitationError(jsonResponse));
throw new Error(response.statusText);
}
dispatch(submitTeammateInvitationSuccess(jsonResponse));
} catch (error) {
if (process.env.NODE_ENV === 'development') {
console.log('Request failed', error);
}
}
};
};
test
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { showAlertError } from '../../../../_helpers/alerts';
jest.mock('../../../../_helpers/alerts');
const middlewares = [thunk];
const createMockStore = configureMockStore(middlewares);
describe('submitTeammateInvitation', () => {
it('dispatches the correct actions on a failed fetch request', () => {
fetch.mockResponse(
JSON.stringify(error),
{ status: 500, statusText: 'Internal Server Error' }
);
const store = createMockStore({});
const expectedActions = [
submitTeammateInvitationRequestObject,
submitTeammateInvitationErrorObject
];
const showAlertError = jest.fn();
return store.dispatch(submitTeammateInvitation(inviteTeammateEndpoint))
.then(() => {
expect(showAlertError).toBeCalled(); // this doesn't work
expect(store.getActions()).toEqual(expectedActions); // this works
});
});
});
You can mock showErrorAlert function manually. Here is the solution:
actionCreators.ts:
import fetch from 'node-fetch';
import { showErrorAlert } from './showErrorAlert';
const SUBMIT_TEAMATE_INVITATION_REQUEST = 'SUBMIT_TEAMATE_INVITATION_REQUEST';
const SUBMIT_TEAMATE_INVITATION_SUCCESS = 'SUBMIT_TEAMATE_INVITATION_SUCCESS';
const SUBMIT_TEAMATE_INVITATION_ERROR = 'SUBMIT_TEAMATE_INVITATION_ERROR';
export const submitTeammateInvitationRequest = data => ({ type: SUBMIT_TEAMATE_INVITATION_REQUEST, payload: { data } });
export const submitTeammateInvitationSuccess = data => ({ type: SUBMIT_TEAMATE_INVITATION_SUCCESS, payload: { data } });
export const submitTeammateInvitationError = data => ({ type: SUBMIT_TEAMATE_INVITATION_ERROR, payload: { data } });
export const submitTeammateInvitation = data => {
const config = {
// config code
};
const inviteTeammateEndpoint = 'https://github.com/mrdulin';
return async dispatch => {
dispatch(submitTeammateInvitationRequest(data));
try {
const response = await fetch(inviteTeammateEndpoint, config);
const jsonResponse = await response.json();
if (!response.ok) {
showErrorAlert(jsonResponse);
dispatch(submitTeammateInvitationError(jsonResponse));
throw new Error(response.statusText);
}
dispatch(submitTeammateInvitationSuccess(jsonResponse));
} catch (error) {
if (process.env.NODE_ENV === 'development') {
console.log('Request failed', error);
}
}
};
};
showErrorAlert.ts:
export function showErrorAlert(jsonResponse) {
console.log(jsonResponse);
}
actionCreators.spec.ts:
import {
submitTeammateInvitation,
submitTeammateInvitationRequest,
submitTeammateInvitationSuccess,
submitTeammateInvitationError
} from './actionCreators';
import createMockStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import fetch from 'node-fetch';
import { AnyAction } from 'redux';
import { showErrorAlert } from './showErrorAlert';
const { Response } = jest.requireActual('node-fetch');
jest.mock('node-fetch');
jest.mock('./showErrorAlert.ts', () => {
return {
showErrorAlert: jest.fn()
};
});
const middlewares = [thunk];
const mockStore = createMockStore<any, ThunkDispatch<any, any, AnyAction>>(middlewares);
describe('submitTeammateInvitation', () => {
it('dispatches the correct actions on a failed fetch request', () => {
const mockedResponse = { data: 'mocked response' };
const mockedJSONResponse = JSON.stringify(mockedResponse);
const mockedData = { data: 'mocked data' };
(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValueOnce(
new Response(mockedJSONResponse, { status: 500, statusText: 'Internal Server Error' })
);
const intialState = {};
const store = mockStore(intialState);
const expectedActions = [
submitTeammateInvitationRequest(mockedData),
submitTeammateInvitationError(mockedResponse)
];
return store.dispatch(submitTeammateInvitation(mockedData)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(showErrorAlert).toBeCalledWith(mockedResponse);
});
});
});
Unit test result with coverage report:
PASS src/stackoverflow/47560126/actionCreators.spec.ts
submitTeammateInvitation
✓ dispatches the correct actions on a failed fetch request (11ms)
-------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files | 89.29 | 50 | 83.33 | 90.91 | |
actionCreators.ts | 89.29 | 50 | 83.33 | 90.91 | 32,35 |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.864s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/47560126