I am pretty new to testing React-Redux and I would like to test my loadUser-action which uses redux-thunk and calls an end point which has an auth middle ware. Here is code I would like to test:
export const loadUser = () => (dispatch, getState) => {
dispatch({ type: USER_LOADING });
axios
.get('/auth/user', tokenConfig(getState))
.then((res) =>
dispatch({
type: USER_LOADED,
payload: res.data,
})
)
.catch((err) => {
console.log(err);
dispatch({
type: LOADING_FAILURE,
});
});
};
export const tokenConfig = (getState) => {
const token = getState().auth.token;
const config = {
headers: {
'Content-type': 'application/json',
},
};
if (token) {
config.headers['x-auth-token'] = token;
}
console.log('CONFIG', config);
return config;
};
And this is my test this far:
import { mockStore } from '../../test/utils/mockStore';
import { USER_LOADED } from '../types/authTypes';
import { loadUser } from './authActions';
describe('loadUser', () => {
fit('gets user', async () => {
const store = mockStore();
const tokenConfig = jest.fn();
await store.dispatch(loadUser());
const actions = store.getActions();
expect(actions[0]).toEqual({ type: USER_LOADED, meta: {} });
});
});
The tokenConfig function must be called in a different way. I can't figure out how!
I would mock axios because you don't want to be doing actual API calls when running unit tests because it would use resources on your server. Also by mocking axios, you don't have to mock tokenConfig.
This is how I have done it on a personal project of mine:
import { mockStore } from '../../test/utils/mockStore';
import { USER_LOADED, LOADING_FAILURE } from '../types/authTypes';
import { loadUser } from './authActions';
import axios from 'axios';
jest.mock('axios'); // mock axios library
describe('loadUser', () => {
fit('gets user', async () => {
const store = mockStore();
axios.get.mockImplementationOnce(() => Promise.resolve({ data: {} })); // mock resolve success
await store.dispatch(loadUser());
const actions = store.getActions();
expect(actions[0]).toEqual({ type: USER_LOADED, payload: {} });
});
it('handles api failure', () => {
const store = mockStore();
axios.get.mockImplementationOnce(() => Promise.reject('Error')); // mock error
await store.dispatch(loadUser());
const actions = store.getActions();
expect(actions[0]).toEqual({ type: LOADING_FAILURE });
});
});
Related
I have a CrudActions.js class:
export default class CrudActions {
constructor(entity, api) {
this.setEntity(entity);
this.setApi(api);
}
setEntity(entity) {
this.entity = entity.toUpperCase();
}
setApi(api) {
this.api = api;
};
getEntity() {
return this.entity;
};
getApi() {
return this.api;
};
fetchItems() {
return dispatch => {
dispatch(
{
type: `TRY_FETCH_${this.getEntity()}_ITEMS`,
}
);
this.getApi()
.fetchItems()
.then(data => {
dispatch({
type: `FETCH_${this.getEntity()}_ITEMS_SUCCEEDED`,
data
});
})
.catch(error => {
dispatch({
type: `FETCH_${this.getEntity()}_ITEMS_FAILED`,
error,
});
})
}
};
}
I extend it with a new class (one class for every route)
import { instance as api } from "../../api/app/Ping";
import CrudActions from "../base/CrudActions";
export default class PingActions extends CrudActions {
constructor() {
super("ping", api);
}
}
export const actions = new PingActions();
I want put under test fetchItems and test that right actions are dispatched.
So, in a Ping.test.js:
import { actions as pingActions } from "../../../../utils/actions/app/PingActions";
import { axiosInstance } from "../../../../utils/api/base/axiosInstance";
import MockAdapter from "axios-mock-adapter";
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const entity = 'ping';
const baseUrl = '/ping';
const dataFetchItems = [
{
app_version: "9.8.7"
}
];
describe('Test PingActions', () => {
let mock;
let store;
beforeEach(() => {
store = mockStore({
ping: {
items: dataFetchItems
}
})
})
beforeAll(() => {
mock = new MockAdapter(axiosInstance);
});
afterEach(() => {
mock.reset();
});
it ('Test can dispatch success actions', () => {
mock.onGet('http://localhost:8000/api/v1'+baseUrl).reply(200, dataFetchItems);
store.dispatch(pingActions.fetchItems());
console.log(store.getActions());
expect(store.getActions()).toContainEqual({
type: "TRY_FETCH_PING_ITEMS",
});
});
it ('Test can dispatch fail actions', () => {
mock.onGet('http://localhost:8000/api/v1'+baseUrl).reply(401);
store.dispatch(pingActions.fetchItems());
console.log(store.getActions());
expect(store.getActions()).toContainEqual({
type: "TRY_FETCH_PING_ITEMS",
});
});
});
With these tests I can cover both case: "TRY_FETCH_PING_ITEMS" and "FETCH_PING_ITEMS_SUCCEEDED" (I see it from coverage).
I cannot understand how get FETCH_PING_ITEMS_SUCCEEDED or FETCH_PING_ITEMS_FAILED actions in store.getActions().
store.getActions() has only TRY_FETCH_PING_ITEMS inside:
PASS src/__tests__/utils/actions/app/PingActions.test.js
● Console
console.log
[ { type: 'TRY_FETCH_PING_ITEMS' } ]
at Object.<anonymous> (src/__tests__/utils/actions/app/PingActions.test.js:46:13)
console.log
[ { type: 'TRY_FETCH_PING_ITEMS' } ]
at Object.<anonymous> (src/__tests__/utils/actions/app/PingActions.test.js:55:13)
I made a new test, without luck:
it ('Test can dispatch success actions', async () => {
mock.onGet('http://localhost:8000/api/v1'+baseUrl).reply(200, dataFetchItems);
await store.dispatch(pingActions.fetchItems());
console.log(store.getActions());
expect(store.getActions()).toContainEqual({
type: "TRY_FETCH_PING_ITEMS",
});
});
But I get...
PASS src/__tests__/utils/actions/app/PingActions.test.js
● Console
console.log
[ { type: 'TRY_FETCH_PING_ITEMS' } ]
at Object.<anonymous> (src/__tests__/utils/actions/app/PingActions.test.js:46:13)
(I miss, every time, the FETCH_PING_ITEMS_SUCCEEDED)
Another test:
it ('Test can dispatch success actions', () => {
mock.onGet('http://localhost:8000/api/v1'+baseUrl).reply(200, dataFetchItems);
return store.dispatch(pingActions.fetchItems()).then(data => console.log(data));
});
But I get
TypeError: Cannot read property 'then' of undefined
Or also:
it ('Test can dispatch success actions', () => {
mock.onGet('http://localhost:8000/api/v1'+baseUrl).reply(200, dataFetchItems);
const data = pingActions.fetchItems().then(data => console.log(data));
});
I get
TypeError: _PingActions.actions.fetchItems(...).then is not a function
The Github Repository: https://github.com/sineverba/body-measurement-frontend
A few bit changes will make it work.
The Problem
You expect that FETCH_PING_ITEMS_SUCCEEDED or FETCH_PING_ITEMS_FAILED actions should be dispatched after the TRY_FETCH_PING_ITEMS action. since both success and failure cases are a promise, so they need to be processed in the proper way (nicely implemented in the CrudActions with then/catch block) but you need to handle these asynchronous actions also in your test case after dispatching the TRY_FETCH_PING_ITEMS.
The Solution
from React testing library documentation:
When in need to wait for any period of time you can use waitFor, to wait for your expectations to pass.
import {waitFor} from '#testing-library/react'
it('Test can dispatch success actions', async () => {
mock.onGet('http://localhost:8000/api/v1' + baseUrl).reply(200);
store.dispatch(pingActions.fetchItems());
expect(store.getActions()).toContainEqual({
type: "TRY_FETCH_PING_ITEMS"
})
await waitFor(() => {
expect(store.getActions()).toContainEqual({
type: "FETCH_PING_ITEMS_SUCCEEDED",
})
})
})
You can also put the fetch ping expectation in the waitFor callback.
await waitFor(() => {
expect(store.getActions()).toContainEqual({
type: "TRY_FETCH_PING_ITEMS"
})
expect(store.getActions()).toContainEqual({
type: "FETCH_PING_ITEMS_SUCCEEDED",
})
})
Note: Don't forget to add async keyword before the callback function in the it method.
Note: For failure case, do the as same as the success case.
Here's a generic example of testing a thunk, hope this helps.
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import getApiClient from './api-client';
import { initialState } from './reducer';
import * as Actions from './actions';
jest.mock('api-client');
jest.mock('actions');
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('getStuff async action', () => {
it('should request data via API', async () => {
const store = mockStore({ stuff: initialState });
// Mock api client method so we're not sending out actual http requests
getApiClient.mockImplementationOnce(() => ({
getStuff: async () => [{ id: '1' }],
}));
// Don't remember if or why this part was necessary, but it was working regardless :D
const Actions = require('./actions');
// Describe the expected sequence of actions dispatched from the thunk
const expected = [
{ type: 'STUFF/REQUEST' },
{
type: 'STUFF/SUCCESS',
payload: { items: [{ id: '1' }] },
},
];
// Dispatch the thunk and wait for it to complete
await store.dispatch(Actions.getStuff('1'));
const dispatchedActions = store.getActions();
expect(dispatchedActions[0]).toEqual(expected[0]);
expect(dispatchedActions[1].payload).toEqual(expect.objectContaining(expected[1].payload));
});
});
In my react application I have an async api call done with axios. And that api call does accept a custom callback.
I am able to test the axios api call using Jest + Enzyme. But not able to test the custom callback method.
Note: I have mocked my axios module.
src/mocks/axios.js
export default {
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} }))
}
auth.api.js
import Axios from 'axios';
import { AUTH_SERVER_URL } from './../../settings';
import { setAuthToken } from '../actions/auth/auth.action';
export const saveUsers = (user, dispatch) => {
const URL = `${AUTH_SERVER_URL}/auth/register`;
Axios.post(URL, user)
.then(response => {
const { data } = response;
const token = {
accessToken: data.access_token,
};
return token;
})
.then(token => dispatch(setAuthToken(token)))
.catch(error => {
if (error.response) {
console.error(error.response.data.message);
}
})
}
And here is my test code.
spec.js
import mockAxios from 'axios';
import { AUTH_SERVER_URL } from './../../settings';
import { saveUsers } from './auth.api';
import { setAuthToken } from '../actions/auth/auth.action';
describe('Authentication API', () => {
it('saveUsers', () => {
const user = { x: 'test' }
const dispatch = jest.fn(); // need to test this dispatch function gets called or not
const response = {
data: {
access_token: 'access_token',
}
};
const expectedToken = {
accessToken: 'access_token',
};
mockAxios.post.mockImplementationOnce(() => Promise.resolve(response));
saveUsers(user, dispatch);
const url = `${AUTH_SERVER_URL}/auth/register`;
expect(mockAxios.post).toHaveBeenCalledTimes(1);
expect(mockAxios.post).toHaveBeenCalledWith(url, user);
console.log(dispatch.mock.calls);
expect(dispatch).toHaveBeenCalledTimes(1); // failed
expect(dispatch).toHaveBeenCalledWith(setAuthToken(expectedToken)); // failed
});
})
Please help me in this
Try to install this package flush-promises.
Then import it in your test file
import flushPromises from 'flush-promises';
And add it before your assertions.
...
await flushPromises();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith(setAuthToken(expectedToken));
And here add async.
it('saveUsers', async () => {
But I'm not sure if it will help.
Thanks to #Jakub Janik for his answer.
Bellow is my answer without using flush-promise package. But it is using the concept behind flush-promise.
import mockAxios from 'axios';
import { AUTH_SERVER_URL } from './../../settings';
import { saveUsers } from './auth.api';
import { setAuthToken } from '../actions/auth/auth.action';
// A helper function can turn that into a promise itself so you don't need to deal with the done callback.
const flushPromises = () => new Promise(resolve => setImmediate(resolve));
describe('Authentication API', () => {
it('saveUsers', async () => {
const user = { x: 'test' }
const dispatch = jest.fn(); // need to test this dispatch function gets called or not
const response = {
data: {
access_token: 'access_token',
}
};
const expectedToken = {
accessToken: 'access_token',
};
mockAxios.post.mockImplementationOnce(() => Promise.resolve(response));
saveUsers(user, dispatch);
const url = `${AUTH_SERVER_URL}/auth/register`;
expect(mockAxios.post).toHaveBeenCalledTimes(1);
expect(mockAxios.post).toHaveBeenCalledWith(url, user);
await flushPromises(); // Magic happens here
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith(setAuthToken(expectedToken));
});
})
I am writing a test for async action creator but encountered a problem where it states "Expected mock function to have been called" with:
[{"type": "GET_LOGIN_SUCCESS", "value": true}]
But it was not called.
I am not sure where exactly the problem is. If anyone could help that will be greatly appreciated.
Here's my actions.js
import { GET_LOGIN_SUCCESS } from './constants'
export const getLoginInfo = () => {
return (dispatch, getState, axiosInstance) => {
return axiosInstance.get('/api/isLogin.json')
.then((res) => {
dispatch({
type: GET_LOGIN_SUCCESS,
value: res.data.data.login
})
console.log('finishing dispatch')
})
}
}
actions.test.js
import { getLoginInfo } from './actions'
import { GET_LOGIN_SUCCESS } from './constants'
describe('async actions', () => {
it('dispatches GET_LOGIN_SUCCESS when getting login finishes', () => {
const axiosInstance = {
get: jest.fn(() => Promise.resolve({ data: { data: {login : true }}}))
}
const dispatch = jest.fn()
getLoginInfo()(dispatch, null, axiosInstance)
expect(dispatch).toHaveBeenCalledWith({
type: GET_LOGIN_SUCCESS,
value: true
})
})
})
The problem is that jest can't know that there are async task involved. So in your case you create a mock that returns a promise, and dispatch is called when the promise is resolved. As JavaScript is single threaded, it first evaluate the code in the test and all async tasks are done afterwards. So you need to make jest aware of the promise by using async/await:
describe('async actions', () => {
it('dispatches GET_LOGIN_SUCCESS when getting login finishes', async() => {
const p = Promise.resolve({ data: { data: {login : true }}}))
const axiosInstance = {
get: jest.fn(() => p
}
const dispatch = jest.fn()
getLoginInfo()(dispatch, null, axiosInstance)
await p // even it sounds not very logically you need to wait here
expect(dispatch).toHaveBeenCalledWith({
type: GET_LOGIN_SUCCESS,
value: true
})
})
As #brian-lives-outdoors points out, getLoginInfo returns the promise as well so you could also just wait for the result of the call:
it('dispatches GET_LOGIN_SUCCESS when getting login finishes', async() => {
const axiosInstance = {
get: jest.fn(() => Promise.resolve({ data: { data: {login : true }}}))
}
const dispatch = jest.fn()
await getLoginInfo()(dispatch, null, axiosInstance)
expect(dispatch).toHaveBeenCalledWith({
type: GET_LOGIN_SUCCESS,
value: true
})
})
There is a epic article that describes the whole topic
I am new to testing using jest and I am stuck on how I can test this piece of code given to show that Axios.post is called when my registerUser is called. I have searched online and don't have a solid solution to post. Would be grateful if a solution could be provided
This is the function I need to test from authAction.js
export const registerUser = (userData, history) => dispatch => {
axios
.post("/api/users/register", userData)
.then(res => history.push("/login")) // re-direct to login on successful register
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
};
I have tried this but it doesn't seem to work.
import * as authActions from './authActions';
import axios from 'axios';
import configureStore from 'redux-mock-store'; //ES6 modules
import thunk from 'redux-thunk';
const middleware = [thunk];
const mockStore = configureStore(middleware);
describe('test register user axios', () => {
it('should give a response of 201 back after it registers user', () => {
var userData = {email: "kamara#fc.come",
name: "Kris Kamara",
password: "adam123",
password2: "adam123"
}
var history = jest.fn();
const initialState = {}
const store = mockStore(initialState)
store.dispatch(authActions.registerUser({userData}, history));
expect(axios).toHaveBeenCalledTimes(1);
});
});
Thanks in advance.
Return the Promise from the function like this:
export const registerUser = (userData, history) => dispatch => {
return axios // <= return the Promise
.post("/api/users/register", userData)
.then(res => history.push("/login")) // re-direct to login on successful register
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
};
...and then you can test it like this:
import * as authActions from './authActions';
import axios from 'axios';
describe('registerUser', () => {
let mock;
beforeEach(() => {
mock = jest.spyOn(axios, 'post');
});
afterEach(() => {
mock.mockRestore();
});
it('should register the user and redirect to login', async () => {
const push = jest.fn();
const history = { push };
const dispatch = jest.fn();
mock.mockResolvedValue(); // mock axios.post to resolve
await authActions.registerUser('the user data', history)(dispatch);
expect(mock).toHaveBeenCalledWith('/api/users/register', 'the user data'); // Success!
expect(history.push).toHaveBeenCalledWith('/login'); // Success!
});
});
export const addOrganizationsData = (params) => (dispatch) => {
dispatch(showSpinner());
return interceptor
.post(`/api/v1/organizations`, params)
.then(({ data }) => {
dispatch(actionTypes.saveOrganization(data));
dispatch(closeSpinner());
})
.catch((err) => {
dispatch(actionTypes.organizationsError(err));
dispatch(closeSpinner());
});
};
I have an action that I want to test in my React/Redux app:
export const SET_SUBSCRIBED = 'SET_SUBSCRIBED'
export const setSubscribed = (subscribed) => {
// return {
// type: SET_SUBSCRIBED,
// subscribed: subscribed
// }
return function(dispatch) {
var url = "https://api.github.com/users/1/repos";
return fetch(url)
.then(function(result) {
if (result.status === 200) {
dispatch({
type: SET_SUBSCRIBED,
subscribed: subscribed
})
return result
}
return 'failed' //todo
})
}
}
Here is my test:
import { setSubscribed } from '../actions/subscriptions'
import nock from 'nock'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from '../actions/subscriptions'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('Action::Subscriptions', () => {
describe('#setSubscribed()', () => {
afterEach(() => {
nock.cleanAll()
})
describe('when subscribed is true', () => {
beforeEach(() =>{
nock('https://api.github.com/')
.get('/users/1/repos')
.reply(200, 'yes')
})
it('returns SET_SUBSCRIBED type and subscribed true', () => {
const store = mockStore({ subscribed: false })
return store.dispatch(actions.setSubscribed(true))
.then(() => {
expect(store.getActions()).toEqual([{type: 'SET_SUBSCRIBED', subscribed: true}])
})
})
})
})
})
When I run the test, I keep getting the error:
TypeError: Network request failed
It looks like my test isn't too happy with nock.
What am I doing incorrectly and how do I successfully stub the fetch() request?