Jest-mock-axios mock interceptors - reactjs

I have the following axios file:
/* eslint-disable no-param-reassign */
import axios from 'axios';
import { baseURL } from './apiClient';
export default function authenticatedApiClient(jwt: string) {
const apiClient = axios.create({
baseURL,
});
apiClient.interceptors.request.use((config) => {
config.headers = config.headers || {};
config.headers.Authorization = `Bearer ${jwt}`;
return config;
});
return apiClient;
}
And the following test:
import React from 'react';
import {
act, render, screen,
} from '#testing-library/react';
import mockAxios from 'jest-mock-axios';
import { BrowserRouter } from 'react-router-dom';
import { AppProvider } from '../common/AppProvider';
import DisplayEditProfileForm from '.';
test('should render edit user profile form', async () => {
const user = {
username: 'admin',
email: 'ad#ad.com',
};
act(() => {
mockAxios.get.mockResolvedValueOnce({ data: user });
});
render(
<BrowserRouter>
<AppProvider>
<DisplayEditProfileForm />
</AppProvider>
</BrowserRouter>,
);
const usernameInputLabel = screen.getByText(/Username/i);
expect(usernameInputLabel).toBeInTheDocument();
const emailInputLabel = screen.getByText(/Email/i);
expect(emailInputLabel).toBeInTheDocument();
const passwordConfirmationInputLabel = screen.getByText(/Password confirmation/i);
expect(passwordConfirmationInputLabel).toBeInTheDocument();
});
We have recently implemented the interceptors, and now my tests throw the following error:
TypeError: Cannot read properties of undefined (reading 'get')
So how can i mock the interceptors? Could someone provide me with a example?
I have also tried this approach with the same results:
act(() => {
jest.mock('axios', () => ({
create: jest.fn(() => ({
get: jest.fn(),
interceptors: {
request: { use: jest.fn(), eject: jest.fn() },
response: { use: jest.fn(), eject: jest.fn() },
},
})),
}));
const mockedAxios = axios as jest.Mocked<typeof axios>;
mockedAxios.get.mockResolvedValueOnce({ data: [{ user }] });
});

Axios is a singleton, meaning that you have a single instance wherever you import it.
It means that if we include it in our tests, it will be the same instance as in the code you are trying to test.
So if you'd import axios in your test code:
import axios from 'axios';
You would have a single axios instance in your component and tests. And you would be able to do anything with it, mocking and stubbing included.
You could mock it with jest with:
jest.mock("axios");
I found a bit of more info on mocking axios with jest here.

Related

jest mock axios doesn't provide proper mock for axios

I'm trying to provide a mock request for this class and then expect that history.push is called with some path.
Start.js
import React from 'react'
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import { ReactComponent as Arrow } from '../../arrow.svg';
export default function Start() {
let history = useHistory();
const doInitializeApp = () => {
axios.get('http://localhost:8080/api/v1/asap/start')
.then(res => {
if (res.data == true) {
history.push('/login')
} else {
alert('something went wrong. Could not start the application')
}
}).catch(err => {
alert('something went wrong. Could not contact the server!')
});
}
return (
<div>
<div className="container">
<div className="content">
<div id="box">
<h1>Welcome</h1>
<Arrow id="next" onClick={doInitializeApp} />
</div>
</div>
</div>
</div>
);
}
And this is my approach for the test
Start.test.js
import React from 'react';
import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Start from '../components/startscreen/Start';
import { ReactComponent as Arrow } from '../arrow.svg';
import axios from "axios";
Enzyme.configure({ adapter: new Adapter() });
describe('Start', () => {
it('test axios get reroute the application to path /login', () => {
const mProps = { history: { push: jest.fn() } };
const wrapper = shallow(<Start {...mProps} />);
const arrow = wrapper.find(Arrow);
const axiosSpy = jest.spyOn(axios, 'get');
//mock axios
jest.mock("axios");
//mock axios response
axios.get.mockResolvedValue({ data: true });
//simulate onclick
arrow.simulate('click');
expect(axiosSpy).toHaveBeenCalled(); --> this pass
expect(mProps.history.push).toBeCalledWith('/login'); --> this doesn't pass
})
});
However, the test did not pass because the actual axios.get(url) doesn't take the response which I mocked and it always come to the .catch(err => ... "Could not contact the server!")
What did I do wrong in here ? Because that the code didn't come to the if (res.data===true) so that I also couldn't test whether the history.push is actually called or not.
Your mocking code is fine. The code in the catch block is getting executed since useHistory() returns undefined (You can confirm this by console.logging the error inside the catch block).
One way to fix it would be to mock useHistory and pass a mock function for history.push. You can then spy on useHistory() to confirm the history.push got called with /login.
import { useHistory } from 'react-router-dom'
// other import statements omitted for brevity
jest.mock('axios')
jest.mock('react-router-dom', () => {
const fakeHistory = {
push: jest.fn()
}
return {
...jest.requireActual('react-router-dom'),
useHistory: () => fakeHistory
}
})
const flushPromises = () => new Promise(setImmediate)
describe('Start component', () => {
test('redirects to /login', async () => {
const pushSpy = jest.spyOn(useHistory(), 'push')
axios.get.mockResolvedValue({ data: true })
const wrapper = shallow(<App />)
const button = wrapper.find(Arrow)
button.simulate('click')
await flushPromises()
expect(pushSpy).toBeCalledWith('/login')
})
})
I'm using setImmediate to wait for the async action to complete as suggested here.

Network error while using API on localhost, with Jest & React Testing Library

I'm using the React testing library and Jest, and trying to mock a GET request to my backend which runs also on my localhost (on different port).
import * as React from "react";
import { render } from "#testing-library/react";
import FetchMock from "jest-fetch-mock";
import MyComponent from "../MyComponent";
describe("MyComponent", () => {
beforeEach(() => {
FetchMock.resetMocks();
});
it("renders correct data", async () => {
const mockData = { data: "foo" };
FetchMock.once(JSON.stringify(mockData))
const { findAllByText } = render(
<MyComponent url={'localhost:4000'} />
);
const text= await findAllByText(/foo/);
expect(text).toHaveLength(1);
});
});
In my tests configuration, I've added this line too:
axios.defaults.baseURL = 'http://localhost:4000';
It seems like there is a problem with using the localhost, as I am getting this error:
console.error node_modules/#testing-library/react/dist/act-compat.js:52
Error: Error: connect ECONNREFUSED 127.0.0.1:80
at Object.dispatchError (path\node_modules\jsdom\lib\jsdom\living\xhr-utils.js:65:19)
at Request.<anonymous> (path\node_modules\jsdom\lib\jsdom\living\xmlhttprequest.js:676:20)
at Request.emit (events.js:201:15)
at Request.onRequestError (path\node_modules\request\request.js:877:8)
at ClientRequest.emit (events.js:196:13)
at Socket.socketErrorListener (_http_client.js:402:9)
at Socket.emit (events.js:196:13)
at emitErrorNT (internal/streams/destroy.js:91:8)
at emitErrorAndCloseNT (internal/streams/destroy.js:59:3)
at processTicksAndRejections (internal/process/task_queues.js:83:17) undefined
This is my component (MyComponent.js):
import React from 'react';
import axios from 'axios';
export default function MyComponent(url) {
const [data, setData] = React.useState('');
React.useEffect(() => {
const getData = async () => {
axios.get(`${url}/foo`)
.then(res => {
setData(res.data)
})
};
getData();
}, []);
return (
<h1>{data}</h1>
);
}
Any ideas?
Regarding to your case, it looks pretty easy to deal with. You don't have to need any other package to mock your endpoint. You can mock axios directly via jest.
Here is the idea (take a look at the inline comments where you need to add):
import * as React from "react";
import { render } from "#testing-library/react";
import axios from "axios";
import MyComponent from "../MyComponent";
// Mock axios directly
jest.mock('axios')
describe("MyComponent", () => {
it("renders correct data", async () => {
const mockData = { data: "foo" };
// Mock returning your value
axios.get.mockResolvedValue(mockData);
const { findAllByText } = render(
<MyComponent url={'localhost:4000'} />
);
const text= await findAllByText(/foo/);
expect(text).toHaveLength(1);
});
});

How to mock an API call with configuration in JEST?

I am using #woocommerce/woocommerce-rest-api package for my api. I am using NextJS and React Redux. Here is my woocommerce configuration:
import WooCommerceRestApi from '#woocommerce/woocommerce-rest-api';
export const wooApi = new WooCommerceRestApi({
url: 'MY_API_URL',
consumerKey: 'MY_CONSUMER_KEY',
consumerSecret: 'MY_CONSUMER_SECRET',
version: 'wc/v3',
queryStringAuth: true,
});
I dispatch an action right away when the component mounts.
Here's how I use the API in my action:
export const fetchMainProductCategories = () => {
return async (dispatch: Dispatch) => {
try {
const response = await wooApi.get(`products/categories?hide_empty=true&parent=0`);
dispatch<FetchMainProductCategories>({
type: CategoryTypes.fetchMainProductCategories,
payload: response.data,
});
} catch (error) {
console.log(error);
}
};
};
Here's my initial test statements so far but I doesn't work:
import React from 'react';
import '../../__mocks__/matchMedia';
import MockCategories from '../../__mocks__/mockCategories';
import { render, cleanup, logDOM } from '#testing-library/react';
import Index from '../../pages/index';
import Root from '../../Root';
import { wooApi } from '../../config';
jest.mock('../../config');
describe('Homepage', () => {
beforeEach(() => {
render(
<Root>
<Index />
</Root>
);
});
afterEach(cleanup);
it('loads Product Categories', async () => {
wooApi.get.mockResolvedValueOnce({
data: MockCategories,
});
logDOM();
// const list = await waitFor(() => screen.getByTestId('category-list'));
});
});
You need to register the get method of the wooApi as a mock, while preserving the other features of the api. ie:
import { wooApi } from '../../config'
import { fetchMainProductCategories } from '../where-it-is-defined'
// mark get method as jest mock
jest.mock('../../config', () => ({
...jest.requireActual('../../config'), // to avoid overriding other methods/features
get: jest.fn(), // override get method of the api
}))
describe('Homepage', () => {
beforeEach(()=>{
wooApi.get.mockResolvedValue({
status: 200,
data: { categories: ['a', 'b'] },
})
test('loads ...', async () => {
const dispatch = jest.fn()
await fetchMainProductCategories()(dispatch)
expect(dispatch).toHaveBeenCalledWith(
{ type: '...',
payload: { categories: ['a', 'b'] }
}
)
})
})
Ref:
Bypassing Module Mocks in Jest
Edited: My bad, by doing jest.spyOn(config.wooApi, 'get') we are only mocking "get" method of a single instance. The following edited code should work
You can also use jest.spyOn to only mock the get method like below
import * as config from '../../config'
jest.spyOn(WooCommerceRestApi.prototype, 'get')
WooCommerceRestApi.prototype.get.mockResolvedValue('...')

Mocking Axios with Jest in React - mock function not being called

I am trying to use Jest for unit testing. Part of the testing is to mock Axios, but for some reason it is not being called.
Here is my /__mocks__/axios.js code:
export default {
post: jest.fn(() => Promise.resolve({})),
};
Here is my test code:
import mockAxios from 'axios';
import { registerUser } from '../../actions/auth';
import user from '../fixtures/user';
describe('Register User', () => {
test('Should call register API and redirect to login', async () => {
const historyMock = { push: jest.fn() };
mockAxios.post.mockImplementationOnce(() => Promise.resolve());
await registerUser(user, historyMock);
expect(mockAxios.post).toHaveBeenCalledTimes(1);
});
});
Also here is the registerUser code:
export const registerUser = (user, history) => dispatch => axios
.post('/users/register', user)
.then(() => history.push('/login'))
.catch((err) => {
dispatch(handleError(err));
});
But for some reason I continue to get the error:
Register User › Should call register API and redirect to login
expect(jest.fn()).toHaveBeenCalledTimes(1)
Expected mock function to have been called one time, but it was called zero times.
35 | await registerUser(user, historyMock);
36 |
> 37 | expect(mockAxios.post).toHaveBeenCalledTimes(1);
Any ideas why the mock is not working?
As #jonrsharpe pointed out in the comments, the registerUser function was returning the function:
dispatch => axios
.post('/users/register', user)
.then(() => history.push('/login'))
.catch((err) => {
dispatch(handleError(err));
});
So in order for this to work, I had to had to mock the store using the redux-mock-store npm module. The new test code looks like:
import mockAxios from 'axios';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { setCurrentUser, registerUser } from '../../actions/auth';
import user from '../fixtures/user';
const defaultStoreState = { errors: {}, auth: { isAuthenticated: false, user: {} } };
const createMockStore = configureMockStore([thunk]);
describe('Register User', () => {
test('Should call register API and redirect to login', (done) => {
const mockStore = createMockStore(defaultStoreState);
const historyMock = { push: jest.fn() };
mockStore.dispatch(registerUser(user, historyMock)).then(() => {
expect(mockAxios.post).toHaveBeenCalledTimes(1);
expect(historyMock.push).toHaveBeenCalledTimes(1);
expect(historyMock.push).toHaveBeenCalledWith('/login');
done();
});
});
});
This gives a passing test now.
I don't think you're setting the mock correctly.
mockAxios.post.mockImplementationOnce
should be changed to
mockAxios.post = jest.fn().mockResolvedValueOnce('bloofblurg');
Then you can double-check that post has been called once and resolved the expected value.
expect(mockAxios.post).resolves.toBe('bloofblurg');
see
https://jestjs.io/docs/en/mock-function-api#mockfnmockresolvedvalueoncevalue
https://jestjs.io/docs/en/expect#resolves

redux - how use nock to test async action

I follow the basic exmaple of redux.org to test async action
action.js
my code is like this:
import axios from 'axios'
export function getGoodDataStart(){
return{
type: "GOOD_DATA_START"
}
}
export function getGoodDataSuccess(payload){
console.log('success', payload)
return {
type: "GOOD_DATA_SUCCESS",
payload: payload
}
}
export function getGoodDataFail(){
return{
type: "GOOD_DATA_FAIL"
}
}
export function getGoodData(){
return (dispatch) => {
dispatch( getGoodDataStart() )
return axios.get('http://www.google.com/list')
.then( response => {
console.log('fake res',response)
dispatch(getGoodDataSuccess (response) )
})
.catch( err => {
console.log('fake err',err)
})
}
}
test.js
import nock from 'nock'
import React from 'react'
import {expect} from 'chai'
import {getGoodData} from 'registerAction'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('Register component', () => {
it('async action', function () {
nock('http://www.google.com')
.get('/list')
.reply(200,'ok!' )
const store = mockStore({
myData: '' ,
})
const expected = [
{type: "GOOD_DATA_START"},
{type: "GOOD_DATA_SUCCESS", payload: 'ok!'}
]
return store.dispatch(getGoodData())
.then( () => {
expect(store.getActions()).to.equal(expected)
})
})
})
The problem I have is, nock is not blocking the request, it lets function getGoodData make real request to google.com. What am I doing wrong?
screen shot of the error:
Here is the demo: https://github.com/craigcosmo/react-redux-test
install: npm i
to test: npm run test
open url: http://localhost:5051/webpack-dev-server/
Typically when testing an action like this you'll want to remove anything that is not part of your action from the equation. In this case by simply using nock, you're not removing axios from the equation and are actually adding unnecessary complexity. By mocking axios with a spy, you avoid making the network call and you also avoid calling axios at all. This allows you to simply assert that axios is called with the correct parameters. The spy can return a promise that allows testing all the promise handling and subsequent action calls. In order to demonstrate this, I needed to add a library that provides spies, so I opted to add 'expect' for both assertions and spies, but you could easily do the same thing with sinon if you want to stick with chai.
Here's an example where you don't need nock at all and you just mock axios with a spy:
import React from 'react'
import * as registerAction from 'registerAction'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import expect from 'expect'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
// set up to mock axios methods
import axios from 'axios'
const _get = axios.get
const fakePayload = { foo: 'bar' };
describe('Register component', () => {
beforeEach(() => {
// replace the .get method temporarily with a spy
axios.get = expect.createSpy().andReturn(Promise.resolve(fakePayload));
})
afterEach(() => {
// restore the get method with our saved const
axios.get = _get;
})
it('async action', function () {
const store = mockStore({
myData: '' ,
})
const expected = [
{type: "GOOD_DATA_START"},
{type: "GOOD_DATA_SUCCESS", payload: fakePayload}
]
return store.dispatch(registerAction.getGoodData())
.then( () => {
expect(store.getActions()).toEqual(expected)
expect(axios.get).toHaveBeenCalled()
expect(axios.get).toHaveBeenCalledWith('http://www.google.com/list')
})
})
})
read https://github.com/node-nock/nock/issues/150
Your tests are doing great on console-
add this two script run on your package.json
"itest": "mocha --compilers js:babel-register -R spec \"test/*.test.js\"",
"itest:watch": "npm run itest -- --watch"
You might need something like this
beforeEach(() => {
nock.disableNetConnect();
});
afterEach(() => {
nock.cleanAll();
nock.enableNetConnect();
});
Enable/Disable real HTTP Request

Resources