React Js test all api calls function? - javascript

I am trying to test my all api calls function by some method but all the time i am getting error, so what is the best way to test my all api calls library and other function, here so far what i did for testing.
Here is get my car functions for api
const api = `${api1}cars/Car
export async function getMyCar(value, to, from, year, month) {
let products = new Promise((resolve, reject) => {
fetch(
`${api}?type=${value ? value : ""}&date__lte=${
to ? to : ""
}&date__gte=${from ? from : ""}&date__year=${
year ? year : ""
}&date__month=${month ? month : ""}`,
config.head
)
.then((response) => {
resolve(response.json());
})
.catch((reject) => console.log(reject));
});
return products;
}
Car.test.js
import mockAxios from "axios";
import { getMyCar } from "../../Service/Car";
import config from "../Main";
describe("Car Components component", () => {
describe("when rendered", () => {
it("should call a fetchData function", (done) => {
getMyCar().then((response) => {
expect(response).toEqual({
data: [],
});
});
expect(mockAxios.request).toHaveBeenCalledWith({
method: "GET",
url: config.car,
Authorization: "Bearer c8NSnS84hIHiPP3PHZ8f5ZqKwv16lA",
});
expect(mockAxios.request).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).not.toHaveBeenCalled();
done();
});
});
});
Another method i tried like this.
import React from "react";
import { shallow } from "enzyme";
import MyDriver from "../MainDashboard/MyDriver/MyDriver";
import axios from "axios";
import Adapter from "enzyme-adapter-react-16";
import React from "react";
import { shallow } from "enzyme";
import MyDriver from "../MainDashboard/MyDriver/MyDriver";
import axios from "axios";
import Adapter from "enzyme-adapter-react-16";
jest.mock("axios");
describe("ToDoList component", () => {
describe("when rendered", () => {
it("should fetch a list of tasks", () => {
const fetchSpy = jest.spyOn(axios, "get");
const toDoListInstance = shallow(<MyDriver />);
expect(fetchSpy).toBeCalled();
});
});
});

Try this... It should work.
global.fetch = jest.fn();
const mockAPICall = (option, data) => global.fetch.mockImplementation(() => Promise[option](data));
describe("Car Components component", () => {
describe("when rendered", () => {
it("should call a fetchData function", async () => {
const mockResponse = {
data: []
};
mockAPICall("resolve", mockResponse);
return getMyCar().then((response) => {
expect(response).toEqual({
data: []
});
});
});
});
});
You can use 'mockAPICall' for other API's too. Don't forget to clear your mocked 'fetch' incase you are using it in other test cases.
global.fetch.mockClear();

Related

Using react hook in custom Axios service with export

I have a created useAxiosPrivate hook and I want to use it in a service function I have created using axios which I used to export diffrent methods. But since its not a functional or class component I get an error react hooks must be called in a react function component or a custom react hook function
useAxiosPrivate.tsx
import { axiosPrivate } from '../api/axios'
import { useEffect } from 'react'
import useRefreshToken from './useRefreshToken'
import useAuth from './useAuth'
const useAxiosPrivate = () => {
const refresh = useRefreshToken()
const { auth }: any = useAuth()
useEffect(() => {
const requestIntercept = axiosPrivate.interceptors.request.use(
(config) => {
config.headers = config.headers ?? {}
if (!config.headers['Authorization']) {
config.headers['Authorization'] = `Bearer ${auth?.accessToken}`
}
return config
},
(error) => Promise.reject(error),
)
const responseIntercept = axiosPrivate.interceptors.response.use(
(response) => response,
async (error) => {
const prevRequest = error?.config
if (
(error?.response?.status === 403 || error?.response?.status === 401) &&
!prevRequest?.sent
) {
prevRequest.sent = true
const newAccessToken = await refresh()
prevRequest.headers['Authorization'] = `Bearer ${newAccessToken}`
return axiosPrivate(prevRequest)
}
return Promise.reject(error)
},
)
return () => {
axiosPrivate.interceptors.request.eject(requestIntercept)
axiosPrivate.interceptors.response.eject(responseIntercept)
}
}, [auth, refresh])
return axiosPrivate
}
export default useAxiosPrivate
I want to use this in auth.service.tsx
import useAxiosPrivate from "../hooks/useAxiosPrivate"
const axiosPrivate = useAxiosPrivate(); <-- 'I want to use this in this'
export const SharedService {
UpdateProfile: async (firstName:string, lastName:string) => {
const response = await axiosPrivate.put('/user/me',{
firstName,
lastName,
})
}
I get error that hooks should be used at top level or inside functional component or class how do I fix it ?
Your service must be a hook as well so it can use other hooks
import useAxiosPrivate from "../hooks/useAxiosPrivate";
export const useSharedService = () => {
const axiosPrivate = useAxiosPrivate();
return {
UpdateProfile: async (firstName: string, lastName: string) => {
const response = await axiosPrivate.put("/user/me", {
firstName,
lastName,
});
},
};
};

Mocking fetch with jest.fn() in React

I'm wondering why I need to put fetch mock logic inside my test to make it work.
Here is simple example:
Component to test with fetch inside useEffect and state update after response:
// Test.jsx
import React, {useEffect, useState} from 'react'
export const Test = () => {
const [description, setDescription] = useState<string | null>(null)
const fetchData = async () => {
const response = await fetch('https://dummyendpoint/');
const parsed = await response.json();
const description = parsed.value;
setDescription(description);
}
useEffect(() => {
fetchData();
}, [])
return (
<div data-testid="description">
{description}
</div>
)
};
export default Test;
Test logic:
// Test.test.js
import React from 'react';
import {render, screen} from '#testing-library/react';
import Test from "./Test";
global.fetch = jest.fn(() => Promise.resolve({
json: () => Promise.resolve({
value: "Testing something!"
})
}));
describe("Test", () => {
it('Should have proper description after data fetch', async () => {
// need to put mock logic here to make it work
render(<Test/>);
const description = await screen.findByTestId('description');
expect(description.textContent).toBe("Testing something!");
});
})
If I keep global.fetch mock at the top of my test file, I keep getting an error:
TypeError: Cannot read property 'json' of undefined
at const parsed = await response.json();
It's really strange that it does not work as it is.
But I was able to fix it by moving the setup into beforeEach block (I assume beforeAll would also work).
It is a common pattern to backup global variable value, override it for tests and restore it back.
import React from 'react';
import { render, screen } from '#testing-library/react';
import Test from "./Test";
describe("Test", () => {
let originalFetch;
beforeEach(() => {
originalFetch = global.fetch;
global.fetch = jest.fn(() => Promise.resolve({
json: () => Promise.resolve({
value: "Testing something!"
})
}));
});
afterEach(() => {
global.fetch = originalFetch;
});
it('Should have proper description after data fetch', async () => {
// need to put mock logic here to make it work
render(<Test />);
const description = await screen.findByTestId('description');
expect(description.textContent).toBe("Testing something!");
});
});

Setting state value coming from axios when using useState() and useEffect() in React

When I run this test the axios call is mocked correctly but setParticipant() never sets the value of the participant variable. I'm guessing it's because it's asynchronous. How do I "wait" for the setParticipant() call to complete before asserting in the test?
participant.tsx
import React, { useEffect, useState } from 'react';
import axios from 'axios';
function EditParticipant(props) {
const [participant, setParticipant] = useState(null)
useEffect(() => {
axios.get(`/api/participants`).then(response => {
console.log(response.data) // prints fine
setParticipant(response.data)
});
}, []);
return (
<div>
{participant ? // always null
<p>{participant.name}</p>
: ''
}
</div>
);
}
participant-spec.tsx
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { act } from 'react-dom/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { EditParticipant } from './participant-edit';
const mock = new MockAdapter(require('axios'));
describe('<Participant/>', () => {
let container
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
unmountComponentAtNode(container);
container.remove();
container = null;
});
it('show bob', async () => {
mock.onGet("/api/participants").reply(200, {name: "bob" });
act(() => {
render(<EditParticipant />, container);
});
expect(container.textContent).toContain("Bob")
});
});
Given you are using async hooks try adding await in front of act to apply resolved promises:
it('show bob', async () => {
mock.onPost("/api/participants").reply(200, {name: "bob" });
await act(async () => {
render(<EditParticipant />, container);
});
expect(container.textContent).toContain("Bob")
});
This is covered briefly in the data fetching portion of the testings recipes documentation.
Hopefully that helps!

how to check axios get function called or not in react js?

I am trying to test below function or in other words I am trying to write unit test cases of below function.But I am getting error _axios.default.get.mockResolvedValueOnce is not a function
import React from "react";
import axios from "axios";
export default () => {
const [state, setState] = React.useState([]);
const fetchData = async () => {
const res = await axios.get("https://5os4e.csb.app/data.json");
setState(res.data);
};
React.useEffect(() => {
(async () => {
await fetchData();
})();
}, []);
return [state];
};
here is my code
https://codesandbox.io/s/awesome-jepsen-5os4e?file=/src/usetabData.test.js
I write unit test case like that
import useTabData from "./useTabData";
import { act, renderHook, cleanup } from "#testing-library/react-hooks";
import mockAxios from "axios";
describe("use tab data", () => {
afterEach(cleanup);
it("fetch tab data", async () => {
mockAxios.get.mockResolvedValueOnce({
data: {
name: "hello"
}
});
await act(async () => renderHook(() => useTabData()));
expect(mockAxios.get).toHaveBeenCalled();
});
});
Code sandbox doesn't support manual mocks as far as I know.
However, your __mock__ is placed in wrong directory structure. It should be a sibling of node_module.
Having said that, easiest way is to use https://github.com/ctimmerm/axios-mock-adapter
import useTabData from "./useTabData";
import { act, renderHook, cleanup } from "#testing-library/react-hooks";
import axios from "axios";
import MockAxiosAdapter from "axios-mock-adapter";
const mockAxios = new MockAxiosAdapter(axios);
afterEach(() => {
cleanup();// this is not needed . its done by testing library after each.
mockAxios.reset();
});
describe("use tab data", () => {
it("fetch tab data", async () => {
mockAxios.onGet(200, { data: { test: "123" } }); // response code and object.
const { result, waitForNextUpdate } = renderHook(() => useTabData());
const [value] = result.current;
// assert value
// assert the axios call by using history object
});
});
You can use history to assert: https://github.com/ctimmerm/axios-mock-adapter#history

React Redux async action testing

I have my test written to test async actions. I'm currently getting the following error TypeError: Cannot read poperty 'then' of undefined and it is pointing to the following line in my code
return store.dispatch(actions.fetchMovies()).then(() => {
Here is my code :
async actions test :
import { createStore, applyMiddleware } from 'redux';
import initialState from '../reducers/initialState';
import rootReducer from '../reducers/index';
import thunk from 'redux-thunk';
import * as actions from './actions';
import * as ActionTypes from '../constants/constants';
import nock from 'nock';
import { expect } from 'chai';
import API_KEY from '../config/config';
const MOVIES_API = 'https://api.themoviedb.org/3/discover/movie?api_key='+API_KEY;
describe('async actions', () => {
afterEach(() => {
nock.cleanAll();
});
it('creates FETCH_MOVIES_SUCCESS when fetching movies is complete', () => {
nock(MOVIES_API)
.get()
.reply(200, {data: {results: [{title: 'Batman vs Superman'}]}});
const expectedActions = [
{ type: ActionTypes.FETCH_MOVIES },
{ type: ActionTypes.FETCH_MOVIES_SUCCESS, data: {results: [{title: 'Batman vs Superman'}]}}
];
const store = createStore(rootReducer, initialState, applyMiddleware(thunk));
return store.dispatch(actions.fetchMovies()).then(() => {
expect(store.getActions()).to.deep.equal(expectedActions);
});
});
});
actions:
import axios from 'axios';
import * as constants from '../constants/constants';
import API_KEY from '../config/config';
export const fetchMovies = () => {
const MOVIES_API = 'https://api.themoviedb.org/3/discover/movie?api_key='+ API_KEY;
return dispatch => {
dispatch({
type: constants.FETCH_MOVIES
});
axios.get(MOVIES_API).then(function(response) {
dispatch({
type: constants.FETCH_MOVIES_SUCCESS,
data: response.data.results
});
})
.catch(function(res) {
dispatch({
type: constants.FETCH_MOVIES_ERROR,
msg: res.message
});
});
};
};
This is the first time testing async actions so I'm not sure what's going wrong.
It's because your action doesn't return a promise - change your action to return a promise that can be awaited. This isn't required, but if you want to know when your API call has completed (i.e. your unit test wants to know in this particular case), then you can return a promise as a convenience side effect of the action:
export const fetchMovies = () => {
const MOVIES_API = 'https://api.themoviedb.org/3/discover/movie?api_key='+ API_KEY;
return dispatch => {
dispatch({
type: constants.FETCH_MOVIES
});
// Return a promise
return axios.get(MOVIES_API).then(function(response) {
dispatch({
type: constants.FETCH_MOVIES_SUCCESS,
data: response.data.results
});
})
.catch(function(res) {
dispatch({
type: constants.FETCH_MOVIES_ERROR,
msg: res.message
});
});
};
}
;
Try using redux-mock-store instead of redux createStore(). This is a mock store for testing async action creators and middleware. The Github page also includes some examples how to use it.
EDIT:
What happens when you modify your action creator so that it returns the result of axios.get(MOVIES_API)?

Categories