A
A
AplexPlex2018-07-31 15:38:54
Node.js
AplexPlex, 2018-07-31 15:38:54

How to test asynchronous actions in Redux?

Hello, I started to study testing and chose the Jest library . I am using the React Redux libraries.
And I use redux-thunk to send asynchronous actions. I know how to test common actions. But I can't figure out how to test asynchronous requests.
Here is the test I wrote:

import {initialState} from 'reducers/commonReducer';
import * as actions from '../../../frontend/src/actions/authActions';
import {asyncConstant} from '../../../frontend/src/constant/asyncConstant';
import {commonConstant} from '../../../frontend/src/constant/commonConstant';

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

describe('async actions', () => {

  it('should dispatch actions of ConstantA and ConstantB', () => {
    const expectedActions = [
      {type: `${commonConstant.REQUES_FOR_AUTH}${asyncConstant.BEGIN}`},
      {type: `${commonConstant.REQUES_FOR_AUTH}${asyncConstant.FAILURE}`},
    ];

    const store = mockStore({ initialState });
    store.dispatch(actions.login('a', 's'));

    expect(store.getActions()).toEqual(expectedActions);
  });
});

But the problem is that it waits for the first dispatch to be received and that's it (in this action, an authentication request is sent to the server and the corresponding dispatch is called based on the results) without waiting for a response.
From here, the question is how to expect a response from the server and process the corresponding dispatch?
Application code ( Link to GitHub ):
spoiler
action/authAction
import { Dispatch } from '../../../node_modules/redux';

import {asyncConstant} from '../constant/asyncConstant';
import {commonConstant} from '../constant/commonConstant';

import {authenticationRequest} from 'api/authApi';
export const login = (email: string, password: string): any => {
        return (dispatch: Dispatch) => {
                dispatch({type: `${commonConstant.REQUES_FOR_AUTH}${asyncConstant.BEGIN}`});
                authenticationRequest(email, password)
                        .then((data: any) => {
                                if (data.auth) {
                                        dispatch({type: `${commonConstant.REQUES_FOR_AUTH}${asyncConstant.SUCCESS}`});
                                } else {
                                        dispatch({type: `${commonConstant.REQUES_FOR_AUTH}${asyncConstant.FAILURE}`, payload: {getServerDataError: true}});
                                }
                        })
                        .catch((error: any) => {
                                console.log(error);
                                dispatch({type: `${commonConstant.REQUES_FOR_AUTH}${asyncConstant.FAILURE}`, payload: {connectServerError: true}});
                        });
        };
};

api/authApi
import axios, { AxiosPromise } from 'axios';
import {config} from '../config';
import {ServerRoters} from '../constant/serverRouters';

export function authenticationRequest(email: string, password: string): AxiosPromise {
  return axios.post(`${config.apiPrefix}:${config.serverPort}/${ServerRoters.auth}`,
    {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      email,
      password,
    })
    .then((response: any) => {
      if (response.status === 200 || 304) {
        return response;
      }
    }).then((response: any) => {
        if (response.data.auth) {
            return {...response.data};
        } else {
            return {...response.data, auth: false };
        }
    });
}

component/Auth
import * as hash from 'object-hash';
import * as React from 'react';

export interface IAuthDispatch {
    login: (email: string, password: string) => any;
}

export class Auth extends React.Component<IAuthDispatch> {
    public state = {
        emailValue: '',
        passwordValue: '',
    };
    public onChangeInput = (e: React.FormEvent<HTMLInputElement>) => {
        const state: any = {...this.state};
        state[e.currentTarget.name] = e.currentTarget.name === 'passwordValue' ? hash(e.currentTarget.value) : e.currentTarget.value;
        this.setState(state);
    }
    public onHandleSubmit = () => {
        console.log(this.state);
        this.props.login(this.state.emailValue, this.state.passwordValue);
    }
    public render() {
        return (
            <React.Fragment>
                <form onSubmit={ (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); } }>
                    <input onChange={this.onChangeInput} type='text' name='emailValue' placeholder='user name' />
                    <input onChange={this.onChangeInput} type='password' name='passwordValue' placeholder='password' />
                    <input onClick={this.onHandleSubmit} type='submit' name='submit'/>
                </form>
            </React.Fragment>
        );
    }
}

constant
export enum asyncConstant {
    'BEGIN'   =   '_BEGIN',
    'SUCCESS' = '_SUCCESS',
    'FAILURE' = '_FAILURE',
}
export enum commonConstant {
    'REQUES_FOR_AUTH' = 'REQUES_FOR_AUTH',
}
export enum ServerRoters {
    auth = 'api/auth/login',
    getRole = 'api/auth/role',
    logout = 'api/auth/logout',
    getTestListData = 'api/student/gettestlist',
    getIssues = 'api/student/gettestissues',
}

container/ContainerAuth
import {connect} from 'react-redux';
import {Dispatch} from 'redux';

import * as actions from 'actions/authActions';
import {Auth, IAuthDispatch} from 'components/Auth';

const mapDispatchToProps = (dispatch: Dispatch): IAuthDispatch => ({
    login: (email: string, password: string) => {
         dispatch(actions.login(email, password));
    },
});

export const ContainerAuth = connect(
  mapDispatchToProps,
)(Auth);

Answer the question

In order to leave comments, you need to log in

2 answer(s)
M
Maxim, 2018-07-31
@AplexPlex

Redux-thunk is well tested ( documentation ).
The bottom line is that you need to test the expectation: so many actions happened in the store, with such and such data and types (which is what you do for Request + Error), you do not have enough mock to request the server.
There is a text and video " Testing redux actions and reducers " with details if you don't like the documentation example.
An example of a login test from the second test task .

D
davidnum95, 2018-07-31
@davidnum95

This is the biggest problem with redux-thunk. It is difficult to test, because you need to mock getStore and dispatch, as well as a request to the server. The only way out is to write your own implementation of store.
As an example: https://michalzalecki.com/testing-redux-thunk-like...

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question