react-testing-library mocking axios.create({}) instance - reactjs

I want to test my api with react-testing-library
And I exporting the instance created by axios.create from a file called apiClient.ts
import axios from 'axios'
const apiClient = axios.create({
baseURL: process.env.REACT_APP_API_URL,
responseType: 'json',
headers: {
'Content-Type': 'application/json',
},
})
export default apiClient
Then use the axios instances I get from apiClient in my users.ts fetchUsersApi
import apiClient from './apiClient'
export interface ITrader {
id: number
name: string
username: string
email: string
address: any
phone: string
website: string
company: any
}
export const fetchTradersApi = async (): Promise<ITrader[]> => {
const response = await apiClient.get<ITrader[]>('/users')
return response.data
}
I created a mocks folder and added axios.ts in it
export default {
get: jest.fn(() => Promise.resolve({ data: {} })),
}
My users.spec.tsx looks like:
import { cleanup } from '#testing-library/react'
import axiosMock from 'axios'
import { fetchTradersApi } from './traders'
jest.mock('axios')
describe.only('fetchTradersApi', () => {
afterEach(cleanup)
it('Calls axios and returns traders', async () => {
axiosMock.get.mockImplementationOnce(() =>
Promise.resolve({
data: ['Jo Smith'],
})
)
const traders = await fetchTradersApi()
expect(traders).toBe([{ name: 'Jo Smith' }])
expect(axiosMock.get).toHaveBeenCalledTimes(1)
expect(axiosMock.get).toHaveBeenCalledWith(`${process.env.REACT_APP_API_URL}/users`)
})
})
I run my test and I get:
Test suite failed to run
TypeError: _axios.default.create is not a function
1 | import axios from 'axios'
2 |
> 3 | const apiClient = axios.create({
Please help me on solving the issue by creating a proper axios mock that work with react-testing-library, Tnx in advance.

After spending an entire day I found a solution for the exact same problem which I was having. The problem which I was having is related to JEST, Node and Typescript combo. Let me brief about the files which are playing role in this:
axios-instance.ts // initializing axios
api.ts // api controller
api.spec.ts // api test files
axios-instance.ts
import axios, { AxiosInstance } from "axios";
const axiosInstance: AxiosInstance = axios.create({
baseURL: `https://example-path/products/`,
headers: {
'Content-Type': 'application/json'
}
});
export default axiosInstance;
api.ts
"use strict";
import {Request, Response, RequestHandler, NextFunction} from "express";
import axiosInstance from "./axios-instance";
/**
* POST /:productId
* Save product by productId
*/
export const save: RequestHandler = async (req: Request, res: Response, next: NextFunction) => {
try {
const response = await axiosInstance.post(`${req.params.id}/save`, req.body);
res.status(response.status).json(response.data);
} catch (error) {
res.status(error.response.status).json(error.response.data);
}
};
api.spec.ts
import { save } from "./api";
import axiosInstance from "./axios-instance";
describe.only("controller", () => {
describe("test save", () => {
let mockPost: jest.SpyInstance;
beforeEach(() => {
mockPost = jest.spyOn(axiosInstance, 'post');
});
afterEach(() => {
jest.clearAllMocks();
});
it("should save data if resolved [200]", async () => {
const req: any = {
params: {
id: 5006
},
body: {
"productName": "ABC",
"productType": "Food",
"productPrice": "1000"
}
};
const res: any = {
status: () => {
return {
json: jest.fn()
}
},
};
const result = {
status: 200,
data: {
"message": "Product saved"
}
};
mockPost.mockImplementation(() => Promise.resolve(result));
await save(req, res, jest.fn);
expect(mockPost).toHaveBeenCalled();
expect(mockPost.mock.calls.length).toEqual(1);
const mockResult = await mockPost.mock.results[0].value;
expect(mockResult).toStrictEqual(result);
});
it("should not save data if rejected [500]", async () => {
const req: any = {
params: {
id: 5006
},
body: {}
};
const res: any = {
status: () => {
return {
json: jest.fn()
}
},
};
const result = {
response: {
status: 500,
data: {
"message": "Product is not supplied"
}
}
};
mockPost.mockImplementation(() => Promise.reject(result));
await save(req, res, jest.fn);
expect(mockPost).toHaveBeenCalled();
const calls = mockPost.mock.calls.length;
expect(calls).toEqual(1);
});
});
});
For the posted requirement we have to mock the "axiosInstance" not the actual "axios" object from the library as we are doing our calling from axiosInstance.
In the spec file we have imported the axiosInstance not the actual axios
import axiosInstance from "./axios-instance";
Then we have created a spy for the post method (get/post/put anything you can spy)
let mockPost: jest.SpyInstance;
Initializing in before each so that each test case will have a fresh spy to start with and also clearing the mocks are needed after each.
beforeEach(() => {
mockPost = jest.spyOn(axiosInstance, 'post');
});
afterEach(() => {
jest.clearAllMocks();
});
Mocking the implementation resolved/reject
mockPost.mockImplementation(() => Promise.resolve(result));
mockPost.mockImplementation(() => Promise.reject(result));
Then calling the actual method
await save(req, res, jest.fn);
Checking for the expected results
expect(mockPost).toHaveBeenCalled();
expect(mockPost.mock.calls.length).toEqual(1);
const mockResult = await mockPost.mock.results[0].value;
expect(mockResult).toStrictEqual(result);
Hope it helps and you can relate the solution with your problem. Thanks

Maybe is too late to register my answer, but it can help others.
What has happened in this case is the context. Your apiClient function runs in another context, so one of the ways is to mock your apiClient instead of the Axios library.
...
import apiClient from 'path-to-your-apiClient';
jest.mock('path-to-your-apiClient');
const mockedApi = apiClient as jest.Mocked<typeof apiClient>;
Now, let's make some changes to your api.spec.ts:
import { save } from "./api";
import axiosInstance from "./axios-instance";
import apiClient from 'path-to-your-apiClient';
jest.mock('path-to-your-apiClient');
const mockedApi = apiClient as jest.Mocked<typeof apiClient>;
describe.only("controller", () => {
describe("test save", () => {
beforeEach(() => {
jest.clearAllMocks();
mockedApi.post.mockeResolvedValue({ your-defalt-value }) // if you need
})
it("should save data if resolved [200]", async () => {
const req: any = {
params: {
id: 5006
},
body: {
"productName": "ABC",
"productType": "Food",
"productPrice": "1000"
}
};
const res: any = {
status: () => {
return {
json: jest.fn()
}
},
};
const result = {
status: 200,
data: {
"message": "Product saved"
}
};
mockedApi.post.mockResolvedValue(result);
await save(req, res, jest.fn);
expect(mockedApi.post).toHaveBeenCalled();
expect(mockedApi.post).toHaveBeenCalledTimes(1);
... // add may assertions as you want
});
....
});
});
Hope this piece of code can help others.

Related

How to mock axios in React with using axios.create function

I'm working on React project where I'm using axios for http requests. I have a separate file with axios configuration like below:
import axios from 'axios'
export default axios.create({
baseURL: " http://localhost:3001",
params: {
}
})
I'm using this in action thunk creators like below:
import streams from "../apis/streams";
export const fetchStreams = () => {
return async(dispatch: ThunkDispatch<void, State, Action>) => {
const response: AxiosResponse<Stream[]> = await streams.get<Stream[]>('/streams');
dispatch({type: ActionType.FETCH_STREAMS, payload: response.data});
}
}
First I created "src/__mocks__/axios.ts" file like:
const mockedAxios: any = jest.createMockFromModule('axios');
mockedAxios.create = jest.fn(() => mockedAxios);
export default mockedAxios;
then I wrote test like below:
import mockedAxios, {AxiosResponse} from "axios";
import streamsApi from '../apis/streams'
import expectedStreams from "../mocks/expectedStreams";
jest.mock('axios')
describe('fetchStreams action', () => {
it('Store is updated correctly', async () => {
const mockedResponse: AxiosResponse = {
data: expectedStreams,
status: 200,
statusText: 'OK',
headers: {},
config: {}
}
mockedAxios.get.mockImplementationOnce(() => {
Promise.resolve(mockedResponse);
})
const results = await streamsApi.get('/streams');
expect(results.data).toBe(mockedResponse.data);
});
});
Unfortunately I've received an error like this:
Why is that? How can I correctly create facke API response in this case?
I would be grateful for help.
Ok, I know what was wrong. I forget to add return before Promise like so:
mockedAxios.get.mockImplementationOnce(() => {
return Promise.resolve(mockedResponse);
})

Jest testing - how to handle JsonWebToken response

I am learning how to test my redux thunk actions and the response from my login includes a randomized JsonWebToken. I've written a variable called expectedActions that matches all the data coming back from the action except how to handle randomized strings (JWT). Any ideas on how to handle this?
-- Also, i need to pass real user information (usename/password) to get a LOGIN_SUCCESS response otherwise the function dispatches the LOGIN_FAIL action. Is that normal?
/* eslint-disable no-undef */
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import fetchMock from 'fetch-mock';
import * as actions from '../../../redux/actions/auth';
const middleware = [thunk];
const mockStore = configureMockStore(middleware);
describe('redux async actions', () => {
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it('returns expected login response', async () => {
const userData = {
username: 'user',
email: 'user#gmail.com',
password: 'password',
};
const config = {
headers: {
'Content-Type': 'application/json',
},
};
fetchMock.getOnce('http://localhost:5000/api/v1/users', {
body: { ...userData },
config,
});
const expectedActions = { payload: { token: '' }, type: 'LOGIN_SUCCESS' };
// the value of the token above in the response is a randomized jwt string
const store = mockStore({});
return store
.dispatch(actions.login('user#gmail.com', 'password'))
.then(() => {
// return of async actions
const actionsResponse = store.getActions();
expect(actionsResponse[0]).toEqual(expectedActions);
});
});
});
Bonus: What is the point of fetchMock ? I borrowed the above code from another StackOverflow question and I have yet to understand what the fetchMock is doing.
I overrode the responses JWT with my own token of "123". I don't know if this is correct though, nor do i ever expect a response to this post.
const middleware = [thunk];
const mockStore = configureMockStore(middleware);
describe('redux async actions', () => {
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it('returns expected login response', async () => {
const expectedActions = {
payload: { token: '123' },
type: 'LOGIN_SUCCESS',
};
const store = mockStore({ alert: [], auth: { token: '123' } });
return store
.dispatch(actions.login('user#gmail.com', 'somePassword'))
.then(() => {
// return of async actions
const actionsResponse = store.getActions();
actionsResponse[0].payload.token = '123';
expect(actionsResponse[0]).toEqual(expectedActions);
});
});
});

How can I do a jest test in this function in React axios?

I guys I created a service in React and I need to test this part of the service, I'm using axios and Jest to do this.
I have the next code in React :
import axios from 'axios';
import Endpoints from './endpoints';
const baseUrl = Endpoints.getBackendEndpoint();
export const validateName = (nameObject, callback) => {
axios.post(`${baseUrl}/validateName`, {...nameObject})
.then(response =>{
response.data
})
.then(data => callback(data));
};
I don't need return the promise because all the work is doing by the callback() function.
This is the code that I have in Jest:
mport moxios from 'moxios';
import * as service from '../service';
import mockResponses from './service.test.json';
import Endpoints from '../endpoints';
const validateObjName = {
Id: 1,
Name: 'Bob',
}
beforeEach(() => {
const baseUrl = Endpoints.getBackendEndpoint();
moxios.stubRequest(
`${baseUrl}/validateName`,
{ ...validateObjName },
{
status: 200,
response: mockResponses.validateForm,
}
);
});
afterEach(() => {
moxios.uninstall();
});
it('validateName()', () => {
service.validateName(validateObjName, jest.fn());
});
It works, but still need to increase the Branch coverage.
Thanks for you help guys :D
To get code coverage the code has to run while a test is running so you will want to return the Promise so you can await it in your test so the then callbacks run during your test.
Also, you can simplify validateName to this:
import axios from 'axios';
import Endpoints from './endpoints';
const baseUrl = Endpoints.getBackendEndpoint();
export const validateName = (nameObject, callback) => {
return axios.post(`${baseUrl}/validateName`, { ...nameObject })
.then(response => callback(response.data));
};
In your test you need to install moxios in your beforeEach and pass the mock response as the second parameter to moxios.stubRequest.
Then use an async test function and await the Promise returned by validateName:
import moxios from 'moxios';
import * as service from '../service';
import mockResponses from './service.test.json';
import Endpoints from '../endpoints';
const validateObjName = {
Id: 1,
Name: 'Bob',
}
beforeEach(() => {
moxios.install(); // install moxios
const baseUrl = Endpoints.getBackendEndpoint();
moxios.stubRequest(
`${baseUrl}/validateName`,
{
status: 200,
response: mockResponses.validateForm
}
); // response is the second argument
});
afterEach(() => {
moxios.uninstall();
});
it('validateName()', () => {
service.validateName(validateObjName, jest.fn());
});
it('validateName()', async () => { // use an async test function
const spy = jest.fn();
await service.validateName(validateObjName, spy); // await the Promise
expect(spy).toHaveBeenCalledWith(mockResponses.validateForm); // Success!
});
That should give you a working test and 100% code coverage.

How can I test the async actions which using 'superagent' in my react project with 'Enzyme' and 'Jest'?

I'm new to the test part of a project.
I got a problem in my personal project. I use 'superagent' to get the information from Api and now I want to write test for it. But I cannot use 'fetch-mock' package which used in the Enzyme example.
Here is my action file.
// getRecommendedProductsActions.js
import request from 'superagent';
export const getRecommendedProducts = () => (dispatch) => {
dispatch(fetchProducts());
return request
.get(URL_PRODUCT_BASE)
.set('Content-Type', 'application/json')
.then(res => dispatch(receiveProducts(res.body)))
.catch(err => dispatch(receiveFailure(err)));
};
Here is my test file.
// test/getRecommendedProducts.test.js
import configureMockStore from 'redux-mock-store';
import fetchMock from 'fetch-mock';
import thunk from 'redux-thunk';
import { getRecommendedProducts } from '../../src/actions/products';
describe('async actions', () => {
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it('creates RECEIVE_PRODUCTS when fetching products has been done', () => {
fetchMock
.get('/products', {
body: httpBody,
headers: { 'content-type': 'application/json' },
});
const expectedActions = successResponse;
const store = mockStore();
return store.dispatch(getRecommendedProducts())
.then(() => expect(store.getActions()).toEqual(expectedActions));
});
And I find that 'superagent' is not fetch based and 'fetch-mock' doesn't work.
I also find a __mocks__/superagent.js file.
// mock for superagent - __mocks__/superagent.js
let mockDelay;
let mockError;
let mockResponse = {
status() {
return 200;
},
ok() {
return true;
},
body: {
walla: true,
},
get: jest.fn(),
toError: jest.fn(),
};
const Request = {
post() {
return this;
},
get() {
return this;
},
send() {
return this;
},
query() {
return this;
},
field() {
return this;
},
set() {
return this;
},
accept() {
return this;
},
timeout() {
return this;
},
end: jest.fn().mockImplementation(function (callback) {
if (mockDelay) {
this.delayTimer = setTimeout(callback, 0, mockError, mockResponse);
return;
}
callback(mockError, mockResponse);
}),
// expose helper methods for tests to set
__setMockDelay(boolValue) {
mockDelay = boolValue;
},
__setMockResponse(mockRes) {
mockResponse = mockRes;
},
__setMockError(mockErr) {
mockError = mockErr;
},
};
module.exports = Request;
Thanks for all help from you guys.

Testing fetch action in react/redux app

Im starting with unit testing and Jest. What I want is to test the action's response after fetching some resources from the db.
This is the action code:
export function loadPortlets() {
return function(dispatch) {
return portletApi.getAllPortlets().then(response => {
dispatch(loadPortletsSuccess(response));
dispatch(hideLoading());
}).catch(error => {
dispatch({ type: null, error: error });
dispatch(hideLoading());
throw(error);
});
};
}
This code is fetching data from:
static getAllPortlets() {
return fetch(`${API_HOST + API_URI}?${RES_TYPE}`)
.then(response =>
response.json().then(json => {
if (!response.ok) {
return Promise.reject(json);
}
return json;
})
);
}
And this is the test:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import fetch from 'isomorphic-fetch';
import fetchMock from 'fetch-mock';
import * as actions from '../portletActions';
import * as types from '../actionTypes';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const mockResponse = (status, statusText, response) => {
return new window.Response(response, {
status: status,
statusText: statusText,
headers: {
'Content-type': 'application/json'
}
});
};
describe('async actions', () => {
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
})
it('calls request and success actions if the fetch response was successful', () => {
window.fetch = jest.fn().mockImplementation(() =>
Promise.resolve(mockResponse(200, null, [{ portlets: ['do something'] }])));
const store = mockStore({ portlets: []});
return store.dispatch(actions.loadPortlets())
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions[0]).toContain({ type: types.LOAD_PORTLETS_SUCCESS });
})
});
});
And this is the result of running the test:
FAIL src\actions\__tests__\portletActions.tests.js
● async actions › calls request and success actions if the fetch response was successful
expect(object).toContain(value)
Expected object:
{"portlets": [// here an array of objects], "type": "LOAD_PORTLETS_SUCCESS"}
To contain value:
{"type": "LOAD_PORTLETS_SUCCESS"}
at store.dispatch.then (src/actions/__tests__/portletActions.tests.js:56:34)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
In the redux docs for this example (https://redux.js.org/recipes/writing-tests), they receive a result containing only the action types executed, but I'm getting the real data and the action inside the array.
So I'm not sure if the code is wrong, or the test, or both!
Thanks in advance, any help is highly appreciated!
You're testing too much with this unit test. I see you are using thunks it looks like so you can change your fetch to be passed as a module to the thunk and do something like this. I used jasmine but it's basically the same thing. You don't want to mock your store here just the action and dispatch. The point of the unit test should be to test the async action, not to test getting real data from the db or redux store interactions so you can stub all that out.
For reference configureStore would look like this...
const createStoreWithMiddleware = compose(
applyMiddleware(thunk.withExtraArgument({ personApi }))
)(createStore);
And the test case...
it('dispatches an action when receiving', done => {
const person = [{ firstName: 'Francois' }];
const expectedAction = {
type: ActionTypes.RECEIVED,
payload: {
people,
},
};
const dispatch = jasmine.createSpy();
const promise = Q.resolve(person);
const personApi = {
fetchPerson: jasmine
.createSpy()
.and.returnValue(promise),
};
const thunk = requestPerson();
thunk(dispatch, undefined, { personApi });
promise.then(() => {
expect(dispatch.calls.count()).toBe(2);
expect(dispatch.calls.mostRecent().args[0]).toEqual(expectedAction);
done();
});
});

Resources