I have a middleware which intercept any actions with a type that include: API_REQUEST. An action with API_REQUEST is created by the apiRequest() action creator. When my middleware intercept an action it makes a request using Axios, if the request succeeded it dispatch the action created by apiSuccess(). If Axios throw after the request, the middleware is going to dispatch the action created with apiError().
Middleware:
const apiMiddleware: Middleware = ({ dispatch }) => next => async (action): Promise<void> => {
next(action);
if (action.type.includes(API_REQUEST)) {
const body = action.payload;
const { url, method, feature } = action.meta;
try {
const response = await axios({ method, url, data: body });
dispatch(apiSuccess({ response, feature }));
} catch (error) {
console.error(error);
dispatch(apiError({ error, feature }));
}
}
};
This is how my api middleware works.
Now I'm wondering how can I test that using Jest. Maybe I can mock Axios so it makes a fake request in the middleware, but how?
Here's the current test file I have:
describe('api middleware', () => {
const feature = 'test_feat';
it('calls next', () => {
const { invoke, next } = create(apiMiddleware);
const action = { type: 'TEST' };
invoke(action);
expect(next).toHaveBeenCalledWith(action);
});
it('dispatch api success on request success', () => {
const { invoke, next, store } = create(apiMiddleware);
const action = actions.apiRequest({ body: null, method: 'GET', url: '', feature });
const data = { test: 'test data' };
jest.mock('axios');
invoke(action);
expect(next).toHaveBeenCalledWith(action);
expect(store.dispatch).toHaveBeenCalledWith(actions.apiSuccess({
response: axios.mockResolvedValue({ data }),
feature,
}));
});
});
create() is just a function that I've taken from this part of the doc. It permit me to mock dispatch, getState and next.
Obviously this doesn't worked but I'm sure there's a way.
Here is the unit test solution:
api.middleware.ts:
import { Middleware } from 'redux';
import axios from 'axios';
import { API_REQUEST } from './actionTypes';
import { apiSuccess, apiError } from './actionCreator';
export const apiMiddleware: Middleware = ({ dispatch }) => (next) => async (action): Promise<void> => {
next(action);
if (action.type.includes(API_REQUEST)) {
const body = action.payload;
const { url, method, feature } = action.meta;
try {
const response = await axios({ method, url, data: body });
dispatch(apiSuccess({ response, feature }));
} catch (error) {
console.error(error);
dispatch(apiError({ error, feature }));
}
}
};
actionTypes.ts:
export const API_REQUEST = 'API_REQUEST';
export const API_REQUEST_SUCCESS = 'API_REQUEST_SUCCESS';
export const API_REQUEST_FAILURE = 'API_REQUEST_FAILURE';
actionCreator.ts:
import { API_REQUEST_SUCCESS, API_REQUEST_FAILURE } from './actionTypes';
export function apiSuccess(data) {
return {
type: API_REQUEST_SUCCESS,
...data,
};
}
export function apiError(data) {
return {
type: API_REQUEST_FAILURE,
...data,
};
}
api.middleware.test.ts:
import { apiMiddleware } from './api.middleware';
import axios from 'axios';
import { MiddlewareAPI } from 'redux';
import { API_REQUEST, API_REQUEST_SUCCESS, API_REQUEST_FAILURE } from './actionTypes';
jest.mock('axios', () => jest.fn());
describe('59754838', () => {
afterEach(() => {
jest.clearAllMocks();
});
describe('#apiMiddleware', () => {
describe('Unit test', () => {
it('should dispatch api success action', async () => {
const store: MiddlewareAPI = { dispatch: jest.fn(), getState: jest.fn() };
const next = jest.fn();
const action = {
type: API_REQUEST,
payload: {},
meta: { url: 'http://localhost', method: 'get', feature: 'feature' },
};
const mResponse = { name: 'user name' };
(axios as jest.Mocked<any>).mockResolvedValueOnce(mResponse);
await apiMiddleware(store)(next)(action);
expect(next).toBeCalledWith(action);
expect(axios).toBeCalledWith({ method: action.meta.method, url: action.meta.url, data: action.payload });
expect(store.dispatch).toBeCalledWith({
type: API_REQUEST_SUCCESS,
response: mResponse,
feature: action.meta.feature,
});
});
it('should dispatch api error action', async () => {
const store: MiddlewareAPI = { dispatch: jest.fn(), getState: jest.fn() };
const next = jest.fn();
const action = {
type: API_REQUEST,
payload: {},
meta: { url: 'http://localhost', method: 'get', feature: 'feature' },
};
const mError = new Error('network error');
(axios as jest.Mocked<any>).mockRejectedValueOnce(mError);
await apiMiddleware(store)(next)(action);
expect(next).toBeCalledWith(action);
expect(axios).toBeCalledWith({ method: action.meta.method, url: action.meta.url, data: action.payload });
expect(store.dispatch).toBeCalledWith({
type: API_REQUEST_FAILURE,
error: mError,
feature: action.meta.feature,
});
});
});
});
});
Unit test results with coverage report:
PASS src/stackoverflow/59754838/api.middleware.test.ts (11.206s)
59754838
#apiMiddleware
Unit test
✓ should dispatch api success action (21ms)
✓ should dispatch api error action (23ms)
console.error src/stackoverflow/59754838/api.middleware.ts:3460
Error: network error
at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59754838/api.middleware.test.ts:42:24
at step (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59754838/api.middleware.test.ts:33:23)
at Object.next (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59754838/api.middleware.test.ts:14:53)
at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59754838/api.middleware.test.ts:8:71
at new Promise (<anonymous>)
at Object.<anonymous>.__awaiter (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59754838/api.middleware.test.ts:4:12)
at Object.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59754838/api.middleware.test.ts:34:46)
at Object.asyncJestTest (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:102:37)
at resolve (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
at new Promise (<anonymous>)
at mapper (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
at promise.then (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
at process._tickCallback (internal/process/next_tick.js:68:7)
-------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 50 | 100 | 100 | |
actionCreator.ts | 100 | 100 | 100 | 100 | |
actionTypes.ts | 100 | 100 | 100 | 100 | |
api.middleware.ts | 100 | 50 | 100 | 100 | 9 |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 12.901s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59754838
Related
I have a axiosInstance.js axios instance:
import axios from "axios"
import { REACT_FE_ACCESS_TOKEN } from "../../constants/constant";
const axiosInstance = axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL,
headers: {
"content-type": "application/json"
},
responseType: "json"
});
axiosInstance.interceptors.request.use((config) => {
const token = localStorage.getItem(REACT_FE_ACCESS_TOKEN);
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
export { axiosInstance };
I call it from a class:
import { axiosInstance as api } from "./axiosInstance";
export default class ApiCrud {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
fetchItems() {
return api.get(`${this.getBaseUrl()}`).then(result => result.data);
}
getBaseUrl() {
return this.baseUrl;
}
}
I want to do a write test (using Create React App and Jest).
This is the axiosInstance.test.js file, that works:
import { axiosInstance } from "../../../../utils/api/base/axiosInstance";
import { REACT_FE_ACCESS_TOKEN } from "../../../../utils/constants/constant";
const token = "a1.b2.c3";
beforeEach(() => {
localStorage.clear();
});
describe('Test API Instance', () => {
it ('Test request interceptor with token', () => {
localStorage.setItem(REACT_FE_ACCESS_TOKEN, token);
expect(localStorage.getItem(REACT_FE_ACCESS_TOKEN)).toBe(token);
const result = axiosInstance.interceptors.request.handlers[0].fulfilled({ headers: {} });
expect(result.headers).toHaveProperty("Authorization");
});
it ('Test request interceptor without token', () => {
const result = axiosInstance.interceptors.request.handlers[0].fulfilled({ headers: {} });
expect(result.headers).not.toHaveProperty("Authorization");
});
});
This is the apiCrud.test.js
import ApiCrud from "../../../../utils/api/base/ApiCrud";
const mockedGet = {
email: "info#example.com",
}
jest.mock('axios', () => {
return {
create: jest.fn(() => ({
get: jest.fn(() => Promise.resolve({ data: mockedGet })),
interceptors: {
request: { use: jest.fn(), eject: jest.fn() },
response: { use: jest.fn(), eject: jest.fn() }
}
}))
}
})
describe('Test API crud', () => {
it ('Test can get base url', () => {
const apiCrud = new ApiCrud('/fake-url');
expect(apiCrud.getBaseUrl()).toBe('/fake-url');
});
it ('Test can fetch items', () => {
const apiCrud = new ApiCrud('/fake-url');
return apiCrud.fetchItems().then(data => {
expect(data).toBe(mockedGet);
})
});
});
But I get
FAIL src/__tests__/utils/api/base/apiCrud.test.js
● Test API crud › Test can fetch items
TypeError: Cannot read property 'then' of undefined
8 |
9 | fetchItems() {
> 10 | return api.get(`${this.getBaseUrl()}`).then(result => result.data);
| ^
11 | }
12 |
13 | getBaseUrl() {
at ApiCrud.fetchItems (src/utils/api/base/ApiCrud.js:10:12)
at Object.<anonymous> (src/__tests__/utils/api/base/apiCrud.test.js:29:20)
So, I think I'm getting wrong with mocking the get for axios, but... How solve?
You are testing ApiCrud class that depends on the ./axiosInstance module. It's simpler to mock direct dependency ./axiosInstance module than indirect
dependency - axios module.
ApiCrud.js:
import { axiosInstance as api } from './axiosInstance';
export default class ApiCrud {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
fetchItems() {
return api.get(`${this.getBaseUrl()}`).then((result) => result.data);
}
getBaseUrl() {
return this.baseUrl;
}
}
ApiCrud.test.js:
import ApiCrud from './ApiCrud';
import { axiosInstance } from './axiosInstance';
const mockedGet = {
email: 'info#example.com',
};
jest.mock('./axiosInstance');
describe('Test API crud', () => {
afterEach(() => {
jest.clearAllMocks();
});
afterAll(() => {
jest.resetAllMocks();
});
it('Test can get base url', () => {
const apiCrud = new ApiCrud('/fake-url');
expect(apiCrud.getBaseUrl()).toBe('/fake-url');
});
it('Test can fetch items', () => {
axiosInstance.get.mockResolvedValueOnce({ data: mockedGet });
const apiCrud = new ApiCrud('/fake-url');
return apiCrud.fetchItems().then((data) => {
expect(data).toBe(mockedGet);
});
});
});
test result:
PASS examples/69531160/ApiCrud.test.js (10.676 s)
Test API crud
✓ Test can get base url (3 ms)
✓ Test can fetch items (1 ms)
------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------------|---------|----------|---------|---------|-------------------
All files | 73.33 | 0 | 80 | 71.43 |
ApiCrud.js | 100 | 100 | 100 | 100 |
axiosInstance.js | 55.56 | 0 | 0 | 55.56 | 13-17
------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 14.978 s
I am trying to unit test a function which sends a post request then the API returns a json object. I am trying to do this using jest and fetch-mock-jest.
Now instead of the expected payload the tested function receives {"size":0,"timeout":0}
and throws error invalid json response body at reason: Unexpected end of JSON input. Pretty sure there is something basic I don't see. I spent way more time on this without any progress than I'd like to admit.
Edit: I am pretty new to jest and unit testing in general, so if someone has a better suggestion to go about mocking fetch, please tell me.
Test File
import fetchMock from 'fetch-mock-jest'
import {
refreshAccessToken, isTokenExpired
} from '../../../lib/access/AccessToken'
describe('AccessToken Component...', () => {
it('...Refreshes AccessToken', async () => {
const responseBody = { accessToken: taiDeveloperTokenValid } // The payload I want the AccessToken.refreshAccessTokento get
setAccessToken(taiDeveloperTokenExpired)
process.env.NEXT_PUBLIC_AUTH_API_HTTPS_URL = 'http://new-api.com'
fetchMock.post(
`${process.env.NEXT_PUBLIC_AUTH_API_HTTPS_URL}/refreshToken`,
new Promise((res) =>
setTimeout(
() =>
res({
status: 200,
body: JSON.stringify(responseBody),
statusText: 'OK',
headers: { 'Content-Type': 'application/json' },
}),
50,
),
),
)
const refreshAccessTokenResponse = await refreshAccessToken()
expect(refreshAccessTokenResponse).toBe(true)
expect(isTokenExpired()).toBe(false)
})
}
Function I am testing
import fetch from 'isomorphic-unfetch'
export const refreshAccessToken = async (): Promise<boolean> => {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_AUTH_API_HTTPS_URL}/refreshToken`,
{
method: 'POST',
credentials: 'include',
},
)
console.log(JSON.stringify(await response)) // this prints {"size":0,"timeout":0}
const data = await response.json()
accessToken = data.accessToken
return true
} catch (error) {
console.log(error) // this prints FetchError { message: 'invalid json response body at reason: Unexpected end of JSON input', type: 'invalid-json'
return false
}
}
You can use jest.mock(moduleName, factory, options) to mock isomorphic-unfetch module by yourself. Don't need fetch-mock-jest module.
E.g.
main.ts:
import fetch from 'isomorphic-unfetch';
export const refreshAccessToken = async (): Promise<boolean> => {
try {
const response = await fetch(`${process.env.NEXT_PUBLIC_AUTH_API_HTTPS_URL}/refreshToken`, {
method: 'POST',
credentials: 'include',
});
const data = await response.json();
const accessToken = data.accessToken;
console.log(accessToken);
return true;
} catch (error) {
console.log(error);
return false;
}
};
main.test.ts:
import { refreshAccessToken } from './main';
import fetch from 'isomorphic-unfetch';
import { mocked } from 'ts-jest/utils';
jest.mock('isomorphic-unfetch');
const fetchMocked = mocked(fetch);
describe('66009798', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should get access token', async () => {
process.env.NEXT_PUBLIC_AUTH_API_HTTPS_URL = 'http://new-api.com';
const data = { accessToken: '123' };
const mResponse = { json: jest.fn().mockResolvedValueOnce(data) };
fetchMocked.mockResolvedValueOnce(mResponse as any);
const actual = await refreshAccessToken();
expect(actual).toBeTruthy();
expect(fetch).toBeCalledWith('http://new-api.com/refreshToken', {
method: 'POST',
credentials: 'include',
});
expect(mResponse.json).toBeCalledTimes(1);
});
});
unit test result:
PASS examples/66009798/main.test.ts (13.604 s)
66009798
√ should get access token (37 ms)
console.log
123
at examples/66009798/main.ts:11:13
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 83.33 | 100 | 100 | 80 |
main.ts | 83.33 | 100 | 100 | 80 | 14-15
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 15.989 s
I’m having trouble mocking axios with Jest and react-testing-library. I’m stuck on an error around axios interceptors and can’t make my way around it.
This is my api.js file:
import axios from 'axios';
const api = axios.create({
baseURL: window.apiPath,
withCredentials: true,
});
api.interceptors.request.use(config => {
const newConfig = Object.assign({}, config);
newConfig.headers.Accept = 'application/json';
return newConfig;
}, error => Promise.reject(error));
The call to api in my component:
const fetchAsync = async endpoint => {
const result = await api.get(endpoint);
setSuffixOptions(result.data.data);
};
Then in my spec file:
jest.mock('axios', () => {
return {
create: jest.fn(),
get: jest.fn(),
interceptors: {
request: { use: jest.fn(), eject: jest.fn() },
response: { use: jest.fn(), eject: jest.fn() },
},
};
});
test('fetches and displays data', async () => {
const { getByText } = render(<Condition {...props} />);
await expect(getByText(/Current milestone/i)).toBeInTheDocument();
});
The test fails with this message:
TypeError: Cannot read property 'interceptors' of undefined
6 | });
7 |
> 8 | api.interceptors.request.use(config => {
| ^
9 | const newConfig = Object.assign({}, config);
10 | newConfig.headers.Accept = 'application/json';
11 |
What am I doing wrong here?
the create method is what creates the api which has the get and interceptors methods. So you need to create a dummy api object:
jest.mock('axios', () => {
return {
create: jest.fn(() => ({
get: jest.fn(),
interceptors: {
request: { use: jest.fn(), eject: jest.fn() },
response: { use: jest.fn(), eject: jest.fn() }
}
}))
}
})
You must mock api.js, not Axios.
import { FuncToCallAPI } from './funcAPI';
import api from './api';
describe('Mock api.ts', () => {
it('should get data', async () => {
const Expected = { status: 200, data: {} };
jest.spyOn(api, 'get').mockResolvedValue(Expected);
const result = await FuncToCallAPI('something');
expect(result).toEqual(Expected);
});
});
Then create a test for api.js, mocking the axios.create as you did before.
I am trying to mock AWS.SNS and I am getting error. I referred posts on StackOverflow and could come up with below. Still, I am getting an error. I have omitted the irrelevant portion. Can someone please help me?
Below is my index.ts
import { SNS } from "aws-sdk";
export const thishandler = async (event: thisSNSEvent): Promise<any> => {
// I have omitted other code that works and not related to issue I am facing.
// I am receiving correct value of 'snsMessagetoBeSent' I verified that.
const response = await sendThisToSNS(snsMessagetoBeSent);
} // thishandler ends here
async function sendThisToSNS(thisMessage: snsAWSMessage) {
const sns = new SNS();
const TOPIC_ARN = process.env.THIS_TOPIC_ARN;
var params = {
Message: JSON.stringify(thisMessage), /* required */
TopicArn: TOPIC_ARN
};
return await sns.publish(params).promise();
}
My test case is below
jest.mock('aws-sdk', () => {
const mockedSNS = {
publish: jest.fn().mockReturnThis(),
promise: jest.fn()
};
return {
SNS: jest.fn(() => mockedSNS),
};
});
import aws, { SNS } from 'aws-sdk';
const snsPublishPromise = new aws.SNS().publish().promise;
import { thishandler } from "../src/index";
describe("async testing", () => {
beforeEach(() => {
jest.restoreAllMocks();
jest.resetAllMocks();
});
it("async test", async () => {
const ENRICHER_SNS_TOPIC_ARN = process.env.ENRICHER_SNS_TOPIC_ARN;
process.env.ENRICHER_SNS_TOPIC_ARN = "OUR-SNS-TOPIC";
const mockedResponseData ={
"Success": "OK"
};
(snsPublishPromise as any).mockResolvedValueOnce(mockedResponseData);
const result = await thishandler(thisSNSEvent);
});
I get error as TypeError: sns.publish is not a function
Here is the unit test solution:
index.ts:
import { SNS } from 'aws-sdk';
export const thishandler = async (event): Promise<any> => {
const snsMessagetoBeSent = {};
const response = await sendThisToSNS(snsMessagetoBeSent);
return response;
};
async function sendThisToSNS(thisMessage) {
const sns = new SNS();
const TOPIC_ARN = process.env.THIS_TOPIC_ARN;
const params = {
Message: JSON.stringify(thisMessage),
TopicArn: TOPIC_ARN,
};
return await sns.publish(params).promise();
}
index.test.ts:
import { thishandler } from './';
import { SNS } from 'aws-sdk';
jest.mock('aws-sdk', () => {
const mSNS = {
publish: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
return { SNS: jest.fn(() => mSNS) };
});
describe('59810802', () => {
let sns;
beforeEach(() => {
sns = new SNS();
});
afterEach(() => {
jest.clearAllMocks();
});
it('should pass', async () => {
const THIS_TOPIC_ARN = process.env.THIS_TOPIC_ARN;
process.env.THIS_TOPIC_ARN = 'OUR-SNS-TOPIC';
const mockedResponseData = {
Success: 'OK',
};
sns.publish().promise.mockResolvedValueOnce(mockedResponseData);
const mEvent = {};
const actual = await thishandler(mEvent);
expect(actual).toEqual(mockedResponseData);
expect(sns.publish).toBeCalledWith({ Message: JSON.stringify({}), TopicArn: 'OUR-SNS-TOPIC' });
expect(sns.publish().promise).toBeCalledTimes(1);
process.env.THIS_TOPIC_ARN = THIS_TOPIC_ARN;
});
});
Unit test results with 100% coverage:
PASS src/stackoverflow/59810802/index.test.ts (13.435s)
59810802
✓ should pass (9ms)
----------|----------|----------|----------|----------|-------------------|
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: 15.446s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59810802
How to code cover then and catch function of a promise inside a function while unit testing using jest? Please see code below.
Service.js
export const userLogin = data => {
return AjaxService.post(
"http://localhost/3000/signin", data
).then(
res => {
return res.data;
},
error => {
return error.response.data;
}
);
};
AjaxService.js
export const AjaxService = {
post: (url, data, headers) => {
return axios({
method: "POST",
url: url,
headers: headers || { "content-type": "application/json" },
data: data
});
}
}
Example.js
class Login extends Component {
handleSubmit = (event) => {
if (this.props.handleSubmit) this.props.handleSubmit();
this.setState({isLoggedIn: true})
userLogin().then((res) => {
// when promise resolve
var response = res;
}, (err) => {
// when promise reject
var error = err;
})
}
render() {
return (
<form id="login-form" onSubmit={(e) => this.handleSubmit(e)} >
<input type="username" />
<input type="password" />
<button type="submit">Login</button>
</form>
)
}
}
Example.test.js
it("test login form submit ", () => {
wrapper = shallow(<Login />);
let instance = wrapper.instance(); // get class instance
instance.handleSubmit(); // will trigger component method
let actualVal = wrapper.state().isLoggedIn; // get state key value
expect(true).to.eql(actualVal);
});
After generating coverage report using --coverage in Jest
We can see that the code inside promise success and error function doesn't get covered as part of unit testing. So please help cover this. Thanks.
Here is the solution, folder structure:
.
├── ajaxService.ts
├── example.spec.tsx
├── example.tsx
└── service.ts
ajaxService.ts:
import axios from 'axios';
export const AjaxService = {
post: (url, data, headers?) => {
return axios({
method: 'POST',
url,
headers: headers || { 'content-type': 'application/json' },
data
});
}
};
service.ts:
import { AjaxService } from './ajaxService';
export const userLogin = data => {
return AjaxService.post('http://localhost/3000/signin', data).then(
res => {
return res.data;
},
error => {
return error.response.data;
}
);
};
example.tsx:
import React, { Component } from 'react';
import { userLogin } from './service';
export interface ILoginProps {
handleSubmit(): void;
}
interface ILoginState {
isLoggedIn: boolean;
}
export class Login extends Component<ILoginProps, ILoginState> {
constructor(props: ILoginProps) {
super(props);
this.state = {
isLoggedIn: false
};
}
public handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (this.props.handleSubmit) {
this.props.handleSubmit();
}
this.setState({ isLoggedIn: true });
const data = {};
return userLogin(data).then(
res => {
console.log(res);
},
err => {
console.error(err);
}
);
}
public render() {
return (
<form id="login-form" onSubmit={e => this.handleSubmit(e)}>
<input type="username" />
<input type="password" />
<button type="submit">Login</button>
</form>
);
}
}
example.spec.tsx:
import React from 'react';
import { shallow } from 'enzyme';
import { Login, ILoginProps } from './example';
import * as service from './service';
describe('Login', () => {
afterEach(() => {
jest.restoreAllMocks();
jest.resetAllMocks();
});
const mockedProps: ILoginProps = { handleSubmit: jest.fn() };
const mockedFormEvent = { preventDefault: jest.fn() };
const mockedUserLoginResponse = 'mocked data';
const mockedUserLoginError = new Error('database error');
it('test login form submit - 1', done => {
const userLoginSpy = jest.spyOn(service, 'userLogin').mockResolvedValueOnce(mockedUserLoginResponse);
const logSpy = jest.spyOn(console, 'log');
const wrapper = shallow(<Login {...mockedProps}></Login>);
wrapper.find('form').simulate('submit', mockedFormEvent);
setImmediate(() => {
expect(mockedFormEvent.preventDefault).toBeCalledTimes(1);
expect(mockedProps.handleSubmit).toBeCalledTimes(1);
expect(wrapper.state('isLoggedIn')).toBeTruthy();
expect(userLoginSpy).toBeCalledWith({});
expect(logSpy).toBeCalledWith(mockedUserLoginResponse);
done();
});
});
it('test login form submit - 2', async () => {
const userLoginSpy = jest.spyOn(service, 'userLogin').mockResolvedValueOnce(mockedUserLoginResponse);
const logSpy = jest.spyOn(console, 'log');
const wrapper = shallow(<Login {...mockedProps}></Login>);
await (wrapper.instance() as any).handleSubmit(mockedFormEvent);
expect(mockedFormEvent.preventDefault).toBeCalledTimes(1);
expect(mockedProps.handleSubmit).toBeCalledTimes(1);
expect(wrapper.state('isLoggedIn')).toBeTruthy();
expect(userLoginSpy).toBeCalledWith({});
expect(logSpy).toBeCalledWith(mockedUserLoginResponse);
});
it('test login error - 1', done => {
const userLoginSpy = jest.spyOn(service, 'userLogin').mockRejectedValueOnce(mockedUserLoginError);
const errorLogSpy = jest.spyOn(console, 'error');
const wrapper = shallow(<Login {...mockedProps}></Login>);
wrapper.find('form').simulate('submit', mockedFormEvent);
setImmediate(() => {
expect(mockedFormEvent.preventDefault).toBeCalledTimes(1);
expect(mockedProps.handleSubmit).toBeCalledTimes(1);
expect(wrapper.state('isLoggedIn')).toBeTruthy();
expect(userLoginSpy).toBeCalledWith({});
expect(errorLogSpy).toBeCalledWith(mockedUserLoginError);
done();
});
});
});
Unit test result with coverage report:
PASS src/stackoverflow/58110463/example.spec.tsx
Login
✓ test login form submit - 1 (16ms)
✓ test login form submit - 2 (3ms)
✓ test login error - 1 (10ms)
console.log node_modules/jest-mock/build/index.js:860
mocked data
console.log node_modules/jest-mock/build/index.js:860
mocked data
console.error node_modules/jest-mock/build/index.js:860
Error: database error
at Suite.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/58110463/example.spec.tsx:14:32)
at addSpecsToSuite (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmine/Env.js:496:51)
at Env.describe (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmine/Env.js:466:11)
at describe (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmine/jasmineLight.js:81:18)
at Object.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/58110463/example.spec.tsx:6:1)
at Runtime._execModule (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-runtime/build/index.js:888:13)
at Runtime._loadModule (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-runtime/build/index.js:577:12)
at Runtime.requireModule (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-runtime/build/index.js:433:10)
at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:201:13
at Generator.next (<anonymous>)
at asyncGeneratorStep (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:27:24)
at _next (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:47:9)
at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:52:7
at new Promise (<anonymous>)
at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:44:12
at _jasmine (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:206:19)
at jasmine2 (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/index.js:60:19)
at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-runner/build/runTest.js:385:24
at Generator.next (<anonymous>)
at asyncGeneratorStep (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-runner/build/runTest.js:161:24)
at _next (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-runner/build/runTest.js:181:9)
at process._tickCallback (internal/process/next_tick.js:68:7)
----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------------|----------|----------|----------|----------|-------------------|
All files | 86.21 | 50 | 63.64 | 84.62 | |
ajaxService.ts | 66.67 | 0 | 0 | 66.67 | 5 |
example.tsx | 100 | 75 | 100 | 100 | 21 |
service.ts | 40 | 100 | 0 | 40 | 4,6,9 |
----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 3.035s, estimated 6s
HTML coverage report:
source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58110463
You just have to use the async await sintax:
it("test login form submit ", async () => {
wrapper = shallow(<Login />);
let instance = wrapper.instance(); // get class instance
await instance.handleSubmit(); // will trigger component method
let actualVal = wrapper.state().isLoggedIn; // get state key value
expect(true).to.eql(isLoggedIn);
});
Then, your test will "wait" until the promise is resolved or rejected and it will run inside the then or the catch. You can learn more about how Jest manages the asynchronous code here.
In addition, if you are going to work with a lot of promises, I recommend you to take a look to the wait-for-expect package. It could really help you to code your tests.