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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With