AplexPlex
@AplexPlex

Как тестировать асинхронные action в Redux?

Здравствуйте начал изучать тестирование и выбрал библиотеку Jest. Использую библиотеки React Redux.
И для отправки асинхронных action использую redux-thunk. Я знаю как тестировать обычные действия. Но не могу понять как протестировать асинхронные запросы.
Вот тест который я написал:
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);
  });
});

Но проблема в том что он ждет получения первого dispatch и все ( в этом действии происходит отправка запроса аутентификации на сервер и по результатам вызывается соответствующий dispatch) не дожидаясь ответа.
От сюда и вопрос как ожидать ответа с сервера и обрабатывать соответствующий dispatch?
Код приложения(Ссылка на 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);

  • Вопрос задан
  • 823 просмотра
Решения вопроса 1
maxfarseer
@maxfarseer
https://maxpfrontend.ru, обучаю реакту и компании
Redux-thunk отлично тестируется (документация).

Суть в том, что вам нужно протестировать ожидание: в store случилось столько-то действий, с такими-то данными и типами (что вы и делаете для Request + Error), у вас не хватает мока для запроса на сервер.

Здесь есть текст и видео "тестирование redux экшенов и редьюсеров" с подробностями, если не очень нравится пример из документации.

Пример теста логина из второго тестового задания.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
@davidnum95
В этом то самая большая проблема redux-thunk. Он сложно поддается тестированию, т.к. нужно мокать getStore и dispatch, а также запрос к серверу. Единственный выход - это написать свою реализацию store.
Как пример: https://michalzalecki.com/testing-redux-thunk-like...
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы