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

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);
})

Related

Error when using the next.js API with RecoilJS

I'm trying to initialise a Recoil atom using the Next API but encountering an error.
The default value is set to the function that makes the call to the Next API endpoint, which then retrieves some data from firebase.
When I then try to use the atom in a component using useRecoilState and log its value, I get this error:
error - TypeError [ERR_INVALID_URL]: Invalid URL
at new NodeError (node:internal/errors:371:5)
at onParseError (node:internal/url:552:9)
at new URL (node:internal/url:628:5)
at dispatchHttpRequest (file:///C:/Users/JoelMcMahon/projects/amt/amtAdmin/amt-admin-utility-v2/node_modules/axios/lib/adapters/http.js:169:20)
at new Promise (<anonymous>)
at httpAdapter (file:///C:/Users/JoelMcMahon/projects/amt/amtAdmin/amt-admin-utility-v2/node_modules/axios/lib/adapters/http.js:105:10)
at Axios.dispatchRequest (file:///C:/Users/JoelMcMahon/projects/amt/amtAdmin/amt-admin-utility-v2/node_modules/axios/lib/core/dispatchRequest.js:46:10)
at Axios.request (file:///C:/Users/JoelMcMahon/projects/amt/amtAdmin/amt-admin-utility-v2/node_modules/axios/lib/core/Axios.js:140:33)
at wrap (file:///C:/Users/JoelMcMahon/projects/amt/amtAdmin/amt-admin-utility-v2/node_modules/axios/lib/helpers/bind.js:5:15)
at eval (webpack-internal:///./src/Modules/getUsers.ts:12:58) {
input: '/api/users/getUsersFromDatabase',
code: 'ERR_INVALID_URL',
page: '/'
}
I've also tried setting the default value of the atom as a selector that makes the query using async await but still get the error.
Here are the relevant files:
atoms.js:
import { atom } from "recoil";
import { getUsers } from "../Modules/getUsers";
export const userListPromise = atom({
key: "userListPromise",
default: getUsers(),
});
getUsers.ts:
import axios from "axios";
export const getUsers = (): Promise<any> => {
return new Promise((resolve, reject) => {
axios({
method: "GET",
url: "/api/users/getUsersFromDatabase",
})
.then((response) => {
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
};
getUsersFromDatabase.ts
import axios from "axios";
import type { NextApiRequest, NextApiResponse } from "next";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const url = //My Cloud Function URL//;
axios({
method: "GET",
url: url,
})
.then((response) => {
res.status(200).json(response.data);
})
.catch((error) => {
res.status(400).json({ message: `Failed to get users: ${error}` });
});
}
UserDisplay.tsx:
import React from "react";
import { useRecoilState } from "recoil";
import { userListPromise } from "../Atoms/atoms";
import { getUsers } from "../Modules/getUsers";
const UserDisplay = () => {
const [userList] = useRecoilState(userListPromise);
console.log(userList);
return (
<div>
</div>
);
};
export default UserDisplay;
If I comment out the lines using the state in UserDisplay.tsx:
const [userList] = useRecoilState(userListPromise);
console.log(userList);
then start the development server, uncomment them and save causing a live reload, then the error does not occur. However, if I then refresh the page or try to start the server initially with those lines uncommented, I get the error.
Any help or guidance would be greatly appreciated.
I'm using next v12.3.1 and recoil v0.7.6

How to dispatch post data object using async thunk redux axios middleware

I am trying to dispatch a post of an object data using async redux axios middleware. I am getting 400 https response prob due to formatting. Here is what I have for my code. Any feedback is great!
import { createAsyncThunk } from '#reduxjs/toolkit';
import axios from 'axios';
export const postResults = createAsyncThunk(
'results/postResults',
async ({ data: TestData[] }) => {
const response = await axios.post(`${url}/data/results`, { data });
return response.data;
}
);
const sendData = async (data) => {
try {
data = {
result: 'passed',
data_id: [0]
};
await dispatch(postResults(data));
} catch (err) {
console.log(err);
}
};
I am using sendData(data) in another function to trigger the event.
const processData = (e) => {
//.....
sendData(data);
}
The output I am getting when I debug under xhr.js request.send(requestData):
requestData: "{\"data\":{\"result\":\"passed\",\"data_id\":[0]}}"
I think the formatting is an issue which I have tried to remove data so I can be read and sent its request as
requestData: "{\"result\":\"passed\",\"data_id\":[0]}"
Any thoughts or feedback!
figured out, I removed the { } so its like this:
const response = await axios.post(${url}/data/results, data);

How to send request using customized Hooks in React Hooks

I was trying to create a custom Hooks for handling input HTTP request from any component by simply calling the useHttpPOSTHandler and want to use .then with Axios but its getting failed and error is
as i am new in react not able to debug this
what i have tried
import { useEffect, useState } from "react";
import axios from "axios";
const useHttpPOSTHandler = ({url , data}) => {
const [httpData, setHttpData] = useState();
const apiMethod = useCallback( ({url , data}) => {
axios
.post(url , data)
.then((response) => {
console.log(response)
console.log(response.data)
setHttpData(response.data);
})
.catch((error) => {
console.log(error);
});
}, [setHttpData])
return [ httpData , apiMethod];
};
export default useHttpPOSTHandler;
The arguments to useHTTPPostHandler are expected to be an object with keys url and data instead you are passing them individually causing a syntax error, wrap them within {}
const getData = useHttpPOSTHandler(
{ url: 'url', data: { "password": userPassword, "username": userName }
});
EDIT: As per your update, you won't see the updated data as soon as you make an API call. It will reflect in the next render
import useHttpPOSTHandler from "...."
const MyFunc = () => {
const [httpData, apiMethod] = useHttpPOSTHandlerdotthen()
const handleSubmit = () => {
apiMethod({url: 'url' , data: { "password": userPassword, "username": userName }})
}
if(httpData){
console.log("entered in api method")
console.log(httpData)
}
return (
<div>
...
</div>
)
}

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

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.

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.

Resources