Mock axios get request with queryParams with moxios - reactjs

I am trying to test my async Redux action which gets data from Firebase.
I use jest and moxios to mock the async call
actionTypes.js
export const FETCH_ORDERS_START = 'FETCH_ORDERS_START'
export const FETCH_ORDERS_SUCCESS = 'FETCH_ORDERS_SUCCESS'
export const FETCH_ORDERS_FAILED = 'FETCH_ORDERS_FAILED'
order.js
import * as actionTypes from './actionTypes'
import axios from './../../axios-orders'
export const fetchOrdersSuccess = (orders) => {
return {
type: actionTypes.FETCH_ORDERS_SUCCESS,
orders: orders,
}
}
export const fetchOrdersFailed = (error) => {
return {
type: actionTypes.FETCH_ORDERS_FAILED,
error: error,
}
}
export const fetchOrdersStart = () => {
return {
type: actionTypes.FETCH_ORDERS_START,
}
}
export const fetchOrders = (token, userId) => {
return dispatch => {
dispatch(fetchOrdersStart())
const queryParams = `?auth=${token}&orderBy="userId"&equalTo="${userId}"`
axios.get('/orders.json' + queryParams)
.then(resp => {
const fetchedData = []
for (let key in resp.data) {
fetchedData.push({
...resp.data[key],
id: key,
})
}
dispatch(fetchOrdersSuccess(fetchedData))
})
.catch( error => dispatch(fetchOrdersFailed(error)))
}
}
In my test, i expect that calling fetchOrders(token, userId) will produce two redux actions: START and SUCCESS
import moxios from 'moxios';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import * as actionTypes from './actionTypes';
import * as actions from './order'
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const ordersMock = {
"-LGyxbZUSr5Q4jboj0uw" : {
"ingredients" : {
"bacon" : 0,
"cheese" : 0,
"meat" : 1,
"salad" : 0
},
}
}
describe('order actions', () => {
beforeEach(function () {
moxios.install();
});
afterEach(function () {
moxios.uninstall();
});
it('creates FETCH_ORDER_SUCCESS after successfuly fetching orders', () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: ordersMock,
});
});
const expectedActions = [
{ type: actionTypes.FETCH_ORDERS_START },
{ type: actionTypes.FETCH_ORDERS_SUCCESS, orders: ordersMock },
];
const store = mockStore({ posts: {} })
store.dispatch(actions.fetchOrders("TOKEN", "USER_ID"))
console.log(store.getActions())
expect(store.getActions()).toEqual(expectedActions);
})
})
Unfortunately it always seems to create SUCCESS and FAILED actions. How to properly mock axios call with queryParameters.
In fetchOrders is use my own axios instance with set base-name:
import axios from 'axios'
const instance = axios.create({
baseURL: 'https://urltofirebase.com'
})
export default instance

Here is the solution only use jestjs and typescript, without maxios module.
order.ts:
import * as actionTypes from './actionTypes';
import axios from 'axios';
export const fetchOrdersSuccess = orders => {
return {
type: actionTypes.FETCH_ORDERS_SUCCESS,
orders
};
};
export const fetchOrdersFailed = error => {
return {
type: actionTypes.FETCH_ORDERS_FAILED,
error
};
};
export const fetchOrdersStart = () => {
return {
type: actionTypes.FETCH_ORDERS_START
};
};
export const fetchOrders = (token, userId) => {
return dispatch => {
dispatch(fetchOrdersStart());
const queryParams = `?auth=${token}&orderBy="userId"&equalTo="${userId}"`;
return axios
.get('/orders.json' + queryParams)
.then(resp => {
dispatch(fetchOrdersSuccess(resp));
})
.catch(error => dispatch(fetchOrdersFailed(error)));
};
};
order.spec.ts:
import thunk, { ThunkDispatch } from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import * as actionTypes from './actionTypes';
import * as actions from './order';
import { AnyAction } from 'redux';
import axios from 'axios';
type State = any;
const middlewares = [thunk];
const mockStore = configureMockStore<State, ThunkDispatch<State, undefined, AnyAction>>(middlewares);
const ordersMock = {
'-LGyxbZUSr5Q4jboj0uw': {
ingredients: {
bacon: 0,
cheese: 0,
meat: 1,
salad: 0
}
}
};
describe('order actions', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('creates FETCH_ORDER_SUCCESS after successfuly fetching orders', () => {
expect.assertions(2);
const getSpy = jest.spyOn(axios, 'get').mockResolvedValueOnce(ordersMock);
const expectedActions = [
{ type: actionTypes.FETCH_ORDERS_START },
{ type: actionTypes.FETCH_ORDERS_SUCCESS, orders: ordersMock }
];
const store = mockStore({ posts: {} });
return store.dispatch(actions.fetchOrders('TOKEN', 'USER_ID')).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(getSpy).toBeCalledWith('/orders.json?auth=TOKEN&orderBy="userId"&equalTo="USER_ID"');
});
});
});
Unit test result with coverage report:
PASS src/stackoverflow/51983850/order.spec.ts (7.79s)
order actions
✓ creates FETCH_ORDER_SUCCESS after successfuly fetching orders (8ms)
----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------------|----------|----------|----------|----------|-------------------|
All files | 88.89 | 100 | 71.43 | 88.89 | |
actionTypes.ts | 100 | 100 | 100 | 100 | |
order.ts | 86.67 | 100 | 71.43 | 86.67 | 12,32 |
----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.753s, estimated 19s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/51983850

Related

_reactQuery.QueryClient is not a constructor

I am trying to start writing tests using jest and react-testing-library but when I try to import a react component which should be wrapped in QueryClientProvider, It throws the error
TypeError: _reactQuery.QueryClient is not a constructor
18 | ]
19 |
> 20 | const createTestQueryClient = () => new QueryClient({
| ^
21 | defaultOptions: {
22 | queries: {
23 | retry: false,
//testUtils.tsx
import { render } from '#testing-library/react'
import { rest } from 'msw'
import * as React from 'react'
import {QueryClient, QueryClientProvider } from 'react-query'
export const handlers = [
rest.get(
'*/react-query',
(req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
name: 'mocked-react-query'
})
)
}
)
]
const createTestQueryClient = () => new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
})
export function renderWithClient(ui: React.ReactElement) {
const testQueryClient = createTestQueryClient()
// const testQueryClient = new QueryClient();
const { rerender, ...result } = render(
<QueryClientProvider client={testQueryClient}>{ui}</QueryClientProvider>
)
return {
...result,
rerender: (rerenderUi: React.ReactElement) =>
rerender(
<QueryClientProvider client={testQueryClient}>{rerenderUi}</QueryClientProvider>
),
}
}
export function createWrapper() {
const testQueryClient = createTestQueryClient()
// const testQueryClient = new QueryClient();
return ({ children }: {children: React.ReactNode}) => (
<QueryClientProvider client={testQueryClient}>{children}</QueryClientProvider>
)
}
//OrgDetails.test.tsx
import { render, screen } from "#testing-library/react";
import {QueryClient, QueryClientProvider} from "react-query";
import OrgDetails from "./OrgDetails";
import { renderWithClient } from './testUtils'
var MockData = {
organization_id: "test",
name: "test",
installations: [
{
ID: 24148217,
CreatedAt: "2022-08-26T09:34:40.220008Z",
UpdatedAt: "2022-08-26T09:34:40.220008Z",
DeletedAt: null,
organizationID: "test",
accountID: 35756816,
targetType: "User",
},
],
environments: null,
kube_integrations: null,
basic_integrations: null,
oidc_integrations: null,
};
jest.mock("react-query", () => ({
useQuery: jest
.fn()
.mockReturnValue({ data: { ...MockData }, isLoading: false, error: {} }),
}));
test("show correct org name", () => {
const result = renderWithClient(<OrgDetails />)
});

Test an asynchronous action with thunk and jest

I can't test an asynchronous action that works with thunk, could one tell me what I'm doing wrong or how could I do it?
File containing the action I want to test: technology.js (action)
import { types } from "../types/types";
import swal from "sweetalert";
export const startFetchTechnologies = () => {
return async (dispatch) => {
try {
dispatch(startLoading());
const res = await fetch(
"http://url.com/techs"
);
const data = await res.json();
dispatch(loadTechnologies(data));
} catch (error) {
await swal("Error", "An error has occurred", "error");
}
dispatch(finishLoading());
};
};
export const loadTechnologies = (data) => ({
type: types.loadTechnologies,
payload: data,
});
export const startLoading = () => ({
type: types.startLoadTechnologies,
});
export const finishLoading = () => ({
type: types.endLoadTechnologies,
});
File containing the tests I want to perform: technology.test.js (test)
import { startFetchTechnologies } from "../../actions/technology";
import { types } from "../../types/types";
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import fetchMock from "fetch-mock";
import expect from "expect"; // You can use any testing library
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe("startFetchTechnologies", () => {
afterEach(() => {
fetchMock.restore();
});
beforeEach(() => {
jest.setTimeout(10000);
});
test("startFetchTechnologies", () => {
// fetchMock.getOnce("/todos", {
// body: { todos: ["do something"] },
// headers: { "content-type": "application/json" },
// });
const expectedActions = [
{ type: types.startLoadTechnologies },
{ type: types.loadTechnologies, payload: "asd" },
{ type: types.endLoadTechnologies },
];
const store = mockStore({});
return store.dispatch(startFetchTechnologies()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions);
});
});
});
The console outputs the following:
FAIL src/__test__/actions/technology.test.js (11.407 s)
startFetchTechnologies
✕ startFetchTechnologies (10029 ms)
● startFetchTechnologies › startFetchTechnologies
: Timeout - Async callback was not invoked within the 10000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 10000 ms timeout specified by jest.setTimeout.Error:
19 | });
20 |
> 21 | test("startFetchTechnologies", () => {
| ^
22 |
23 | // fetchMock.getOnce("/todos", {
24 | // body: { todos: ["do something"] },
I have tried increasing the timeout to 30000 and the test keeps failing.
I hope you can help me!
I have made the test pass but I am not sure if I am doing it correctly, could someone tell me if it is well done?
Thank you!
import { startFetchTechnologies } from "../../actions/technology";
import { types } from "../../types/types";
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import fetchMock from "fetch-mock";
import expect from "expect"; // You can use any testing library
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe("startFetchTechnologies", () => {
beforeEach(() => {
// jest.setTimeout(10000);
});
afterEach(() => {
fetchMock.restore();
});
test("startFetchTechnologies", () => {
fetchMock.getOnce("https://url.com/tech", {
body: { payload: ['asd'] },
headers: { "content-type": "application/json" },
});
const expectedActions = [
{ type: types.startLoadTechnologies },
{ type: types.loadTechnologies, payload: {payload: ['asd']} },
{ type: types.endLoadTechnologies },
];
const store = mockStore({});
return store.dispatch(startFetchTechnologies()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions);
});
});
});

Mocking snackbar using jest

I try to test the following code
import { useSnackbar, VariantType, WithSnackbarProps } from 'notistack';
import React from 'react';
interface IProps {
setUseSnackbarRef: (showSnackbar: WithSnackbarProps) => void;
}
const InnerSnackbarUtilsConfigurator: React.FC<IProps> = (props: IProps) => {
props.setUseSnackbarRef(useSnackbar());
return null;
};
let useSnackbarRef: WithSnackbarProps;
const setUseSnackbarRef = (useSnackbarRefProp: WithSnackbarProps) => {
useSnackbarRef = useSnackbarRefProp;
};
export const SnackbarUtilsConfigurator = () =>
<InnerSnackbarUtilsConfigurator setUseSnackbarRef={setUseSnackbarRef} />;
export default {
success(msg: string) {
this.toast(msg, 'success');
},
warning(msg: string) {
this.toast(msg, 'warning');
},
info(msg: string) {
this.toast(msg, 'info');
},
error(msg: string) {
this.toast(msg, 'error');
},
toast(msg: string, variant: VariantType = 'default') {
useSnackbarRef.enqueueSnackbar(msg, { variant });
},
};
if I mock the complete notistack object
import React from "react";
import {render as testingRender} from '#testing-library/react';
import { SnackbarProvider} from 'notistack';
import SnackbarUtils,{SnackbarUtilsConfigurator} from './SnackbarUtils';
jest.mock('notistack');
beforeEach(() =>{
});
it('DownloadDialog renders correctly - open=true', async () => {
const component = await testingRender(<SnackbarProvider maxSnack={3}><SnackbarUtilsConfigurator /></SnackbarProvider>);
SnackbarUtils.success("success");
});
I get an error message:
TypeError: Cannot read property 'enqueueSnackbar' of undefined
33 | },
34 | toast(msg: string, variant: VariantType = 'default') {
> 35 | useSnackbarRef.enqueueSnackbar(msg, { variant });
| ^
36 | },
37 | };
because useSnackbarRef.
How can i mock only the useSnackbar method of notitstack, so that i can check if enqueueSnackbar is called with the correct values?
This works for me. Place this outside all tests (top level scope):
const mockEnqueue = jest.fn();
jest.mock('notistack', () => ({
...jest.requireActual('notistack'),
useSnackbar: () => {
return {
enqueueSnackbar: mockEnqueue
};
}
}));

axios.create of jest's axios.create.mockImplementation returns undefined

I have written a test for component CategoryListContainer for just testing axios get call in it by mocking axios as below :
CategoryListContainer.test.js
import React from 'react';
import { render, cleanup, waitForElement } from '#testing-library/react';
import { Provider } from 'react-redux';
import store from '../../Store';
import axios from 'axios';
import CategoryListContainer from './CategoryListContainer';
jest.mock('axios', () => ({
create: jest.fn(),
}));
const products = {
data: [
{
id: '0',
heading: 'Shirt',
price: '800',
},
{
id: '1',
heading: 'Polo tees',
price: '600',
},
],
};
afterEach(cleanup);
const renderComponent = () =>
render(
<Provider store={store()}>
<CategoryListContainer />
</Provider>
);
test('render loading state followed by products', async () => {
axios.create.mockImplementation((obj) => ({
get: jest.fn(() => Promise.resolve(products)),
}));
const { getByText } = renderComponent();
await waitForElement(() => {
expect(getByText(/loading/i)).toBeInTheDocument();
});
});
As we see that in test 'render loading state followed by products' I wrote mock implemenation for axios.create as axios.create.mockImplementation((obj) => ({ get: jest.fn(() => Promise.resolve(products)), }));
Now when I use axios.create in axiosInstance.js as below :
import axios from 'axios';
const axiosInstance = axios.create({
headers: {
Accept: 'application/json',
ContentType: 'application/json',
authorization: '',
},
});
console.log(axiosInstance);
export default axiosInstance;
console.log(axiosInstance) shows undefined and therefore I'm getting the below error on running the test :
TypeError: Cannot read property 'get' of undefined
4 | const fetchCategories = () => async (dispatch) => {
5 | const response = await axiosInstance
> 6 | .get('/api/category/all')
| ^
7 | .catch((error) => {
8 | dispatch(fetchErrorAction(error));
9 | if (error.message.split(' ').pop() == 504) {
console.log src/backendApiCall/axiosInstance.js:9
undefined
I want to understand why console.log(axiosInstance) shows undefined . And the solution to making the test successful with making minimum changes to code .
because 'create' return jest function so it does not has '.get'.
You can use this
jest.mock('axios', () => {
return {
create: () => {
return {
get: jest.fn()
}}
};
});
then, you can set mock value
axiosInstance.get.mockReturnValueOnce({
data : {}
})

How to write an async redux test with firebase login?

I have no idea how to write a test for this type of problem
auth.js
export const onLogin = ({email, password}) => async dispatch => {
try {
const user = await firebase.auth().signInWithEmailAndPassword(email, password)
dispatch(loginSuccess(user))
} catch (error) {
console.error(error)
dispatch(loginError(error))
}
}
and what I have so far in my auth.test.js
it(`creates ${LOGIN_SUCCESS} when login is successful`, () => {
const expectedAction = {
type: LOGIN_SUCCESS,
payload: { user: { email: 'test#test.com', emailVerified: true, displayName: 'test' } }
};
const store = mockStore({ type: null, payload: null });
const form = { email: 'test#test.com', password: 'test' };
return store.dispatch(actions.onLogin(form)).then(() => {
expect(store.getActions()).toEqual(expectedAction);
});
});
Can anyone help me out?
Here is the solution:
auth.ts:
import firebase from './firebase';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_ERROR = 'LOGIN_ERROR';
export const loginSuccess = user => ({ type: LOGIN_SUCCESS, payload: { user } });
export const loginError = error => ({ type: LOGIN_ERROR, payload: error, error: true });
export const onLogin = ({ email, password }) => async dispatch => {
try {
const user = await firebase.auth().signInWithEmailAndPassword(email, password);
dispatch(loginSuccess(user));
} catch (error) {
console.error(error);
dispatch(loginError(error));
}
};
In order to keep it simple, I simulate the firebase module. You can use the real one.
firebase.ts:
const firebase = {
auth() {
return firebase;
},
async signInWithEmailAndPassword(email: string, password: string): Promise<any> {
return firebase;
}
};
export default firebase;
auth.test.ts:
import { LOGIN_SUCCESS, LOGIN_ERROR } from './auth';
import * as actions from './auth';
import createMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import firebase from './firebase';
jest.mock('./firebase.ts', () => {
return {
auth: jest.fn().mockReturnThis(),
signInWithEmailAndPassword: jest.fn().mockReturnThis()
};
});
const initialState = {};
const middlewares = [thunk];
const mockStore = createMockStore(middlewares);
describe('auth', () => {
const form = { email: 'test#test.com', password: 'test' };
let store;
beforeEach(() => {
store = mockStore(initialState);
});
describe('#onLogin', () => {
it('should login success', () => {
expect.assertions(2);
const mockedUser = { email: 'test#test.com', emailVerified: true, displayName: 'test' };
(firebase.auth().signInWithEmailAndPassword as jest.MockedFunction<
typeof firebase.signInWithEmailAndPassword
>).mockResolvedValueOnce(mockedUser);
const expectedAction = [
{
type: LOGIN_SUCCESS,
payload: { user: mockedUser }
}
];
return store.dispatch(actions.onLogin(form)).then(() => {
expect(store.getActions()).toEqual(expectedAction);
expect(firebase.auth().signInWithEmailAndPassword).toBeCalledWith(form.email, form.password);
});
});
it('should login error', () => {
expect.assertions(2);
const mockedError = new Error('firebase error');
(firebase.auth().signInWithEmailAndPassword as jest.MockedFunction<
typeof firebase.signInWithEmailAndPassword
>).mockRejectedValueOnce(mockedError);
const expectedAction = [
{
type: LOGIN_ERROR,
payload: mockedError,
error: true
}
];
return store.dispatch(actions.onLogin(form)).then(() => {
expect(store.getActions()).toEqual(expectedAction);
expect(firebase.auth().signInWithEmailAndPassword).toBeCalledWith(form.email, form.password);
});
});
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/52550469/auth.test.ts
auth
#onLogin
✓ should login success (6ms)
✓ should login error (7ms)
console.error src/stackoverflow/52550469/auth.ts:3341
Error: firebase error
at Object.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/52550469/auth.test.ts:48:27)
at Object.asyncJestTest (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:102:37)
at resolve (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
at new Promise (<anonymous>)
at mapper (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
at promise.then (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
at process._tickCallback (internal/process/next_tick.js:68:7)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
auth.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.086s, estimated 4s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/52550469

Resources