React Redux async action testing - javascript

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)?

Related

React testing library with redux toolkit createAsyncThunk (How to enable Promise.race in js?)

I have create createAsyncThunk to fetch the data and while dispatching thunk function, promise is being resolved with rejected action type. Does anyone have idea what's happening? It is working fine testing on browser but facing issue in unit tests and throwing error Promise.race is not a function
How to enable Promise.race in js?
import { createAsyncThunk } from '#reduxjs/toolkit';
import axios from 'axios';
const myFunc = createAsyncThunk('returns ID', async (nameAndEmail) => {
const response = await axios.post('/backendroute', nameAndEmail);
return response.data.id;
});
export { myFunc };
Unit test code
import { myFunc } from './thunk';
import { configureStore } from '#reduxjs/toolkit';
import axios from 'axios';
describe('67087596', () => {
it('should pass', async () => {
const nameAndEmail = {
name: 'John Smith',
email: '123#123.com',
};
const postSpy = jest.spyOn(axios, 'post').mockResolvedValueOnce({ data: { id: '1' } });
const store = configureStore({
reducer: function (state = '', action) {
switch (action.type) {
case 'returns ID/fulfilled':
return action.payload;
default:
return state;
}
},
});
await store.dispatch(myFunc(nameAndEmail));
expect(postSpy).toBeCalledWith('/backendroute', nameAndEmail);
const state = store.getState();
expect(state).toEqual('1');
});
});

React Js test all api calls function?

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

redux-react error: Actions must be plain objects

i`m learning redux and after setting all the setup i get this message:
Error: Actions must be plain objects. Use custom middleware for async actions.
I readed 10 different issues with this mistake but not single solution works for me. here is my project on github, in case problem not in following code:https://github.com/CodeNinja1395/Test-task-for-inCode/tree/redux
store:
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
export default store;
actions:
import {FETCH_DATA} from './types';
export const fetchData = () => dispatch => {
fetch('https://raw.githubusercontent.com/CodeNinja1395/Test-task-for-inCode/master/clients.json')
.then(posts =>
dispatch({
type: FETCH_DATA,
payload: posts
})
);
};
You use an old version of redux - v1, while the new version is v4. When you upgrade to v4 it works.
npm install redux#^4.0.0
You're not returning your fetch() call. You have to make sure you return your fetch call inside of the action creator.
export function fetchData() {
return function(dispatch) {
return fetch( ... ).then( ... ).catch( ... )
}
}
export const fetchData = (params) => dispatch => {
dispatch(beginAjaxCall());
return fetch(...).then(response => {
dispatch(someFunction(response));
}).catch(error => {
throw(error);
});
}
export const loadIncidents = (params) => dispatch => {
dispatch(beginAjaxCall());
return IncidentsApi.getIncidents(params).then(incidents => {
dispatch(loadIncidentsSuccess(incidents));
}).catch(error => {
throw(error);
});
}
The top is ES5, middle ES6, and third is an ES6 version of a function that I used in a project.
Hope that helps.

Redux-Thunk - Async action creators Promise and chaining not working

I am trying to dispatch an action. I found working examples for some actions, but not as complex as mine.
Would you give me a hint? What am I doing wrong?
I am using TypeScript and have recently removed all typings and simplified my code as much as possible.
I am using redux-thunk and redux-promise, like this:
import { save } from 'redux-localstorage-simple';
import thunkMiddleware from 'redux-thunk';
import promiseMiddleware from 'redux-promise';
const middlewares = [
save(),
thunkMiddleware,
promiseMiddleware,
];
const store = createStore(
rootReducer(appReducer),
initialState,
compose(
applyMiddleware(...middlewares),
window['__REDUX_DEVTOOLS_EXTENSION__'] ? window['__REDUX_DEVTOOLS_EXTENSION__']() : f => f,
),
);
Component - Foo Component:
import actionFoo from 'js/actions/actionFoo';
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Foo {
constructor(props) {
super(props);
this._handleSubmit = this._handleSubmit.bind(this);
}
_handleSubmit(e) {
e.preventDefault();
this.props.doActionFoo().then(() => {
// this.props.doActionFoo returns undefined
});
}
render() {
return <div onClick={this._handleSubmit}/>;
}
}
const mapStateToProps = ({}) => ({});
const mapDispatchToProps = {
doActionFoo: actionFoo,
};
export { Foo as PureComponent };
export default connect(mapStateToProps, mapDispatchToProps)(Foo);
Action - actionFoo:
export default () => authCall({
types: ['REQUEST', 'SUCCESS', 'FAILURE'],
endpoint: `/route/foo/bar`,
method: 'POST',
shouldFetch: state => true,
body: {},
});
Action - AuthCall:
// extremly simplified
export default (options) => (dispatch, getState) => dispatch(apiCall(options));
Action - ApiCall:
export default (options) => (dispatch, getState) => {
const { endpoint, shouldFetch, types } = options;
if (shouldFetch && !shouldFetch(getState())) return Promise.resolve();
let response;
let payload;
dispatch({
type: types[0],
});
return fetch(endpoint, options)
.then((res) => {
response = res;
return res.json();
})
.then((json) => {
payload = json;
if (response.ok) {
return dispatch({
response,
type: types[1],
});
}
return dispatch({
response,
type: types[2],
});
})
.catch(err => dispatch({
response,
type: types[2],
}));
};
From redux-thunk
Redux Thunk middleware allows you to write action creators that return
a function instead of an action
So it means that it doesn't handle your promises. You have to add redux-promise for promise supporting
The default export is a middleware function. If it receives a promise,
it will dispatch the resolved value of the promise. It will not
dispatch anything if the promise rejects.
The differences between redux-thunk vs redux-promise you can read here
Okay, after several hours, I found a solution. redux-thunk had to go first before any other middleware. Because middleware is called from right to left, redux-thunk return is last in chain and therefore returns the Promise.
import thunkMiddleware from 'redux-thunk';
const middlewares = [
thunkMiddleware,
// ANY OTHER MIDDLEWARE,
];
const store = createStore(
rootReducer(appReducer),
initialState,
compose(
applyMiddleware(...middlewares),
window['__REDUX_DEVTOOLS_EXTENSION__'] ? window['__REDUX_DEVTOOLS_EXTENSION__']() : f => f,
),
);

Can't get a successful test with async ajax requests using nock and tape

I'm trying to write a simple login in React-Redux and I'm running into a problem when I try and test my login action.
I've been following guides from official Redux documentation, guides on how to use fetch, and guides on how to test async/ajax actions but when I write my own ajax call I can't seem to pass the test I have written.
I'm still quite new to React/Redux/Nock/Tape/Fetch so this may be a super rookie question and I do apologise for that.
My LoginActions.js:
import { API_URL } from '../../../globals.js';
import fetch from 'isomorphic-fetch';
/*
* action types
*/
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_FAILED = 'LOGIN_FAILED';
export const LOGIN_ATTEMPT = 'LOGIN_ATTEMPT';
/*
* action creators
*/
export function loginAttemptSuccess({ priviledge, userId, username }) {
return { type: LOGIN_SUCCESS, priviledge, userId, username };
}
export function loginAttemptFailed({ error }) {
return { type: LOGIN_FAILED, error };
}
export function loginAttempt({ username, password }) {
// const PostData = { username, password };
return function (dispatch) {
return fetch(`${API_URL}/login/login.php`, {
method: 'GET'
})
.then(response => dispatch({ type: LOGIN_SUCCESS }))
.catch(error => dispatch({ type: LOGIN_FAILED }));
};
}
My LoginActions.spec.js:
import { API_URL } from '../../../globals.js';
import test from 'tape';
import configureStore from 'redux-mock-store';
import thunkMiddleware from 'redux-thunk';
import sinon from 'sinon';
import nock from 'nock';
import * as LoginActions from './LoginActions.js';
test('Login Attempt on success should return true', (t) => {
t.plan(1);
nock(`${API_URL}`)
.get('/login/login.php')
.reply(200, { body: { todos: ['do something'] }})
const initialState = { todos: [] };
const store = mockStore(initialState);
const dispatch = sinon.spy(store, 'dispatch');
const fn = LoginActions.loginAttempt('one', 'two');
fn(dispatch, store.getState);
t.true(dispatch.calledWith({ type: 'FETCH_TODOS_SUCCESS' }))
});
Result from testem:
# Login Attempt on success should return true
not ok 4 should be truthy
---
operator: ok
expected: true
actual: false
...

Categories