如何测试异步动作?

How to test asynchronous action?

我有一个动作:

export const GetChatList = userStatus => {
  return dispatch => {
    dispatch({
      type: MessagesActionTypes.GET_MESSAGES_LIST.REQUEST,
      payload: {}
    });

    axios
      .get(config.apiUrl + config.methods.getMessagesList, { params: { accountType: userStatus } })
      .then(res => {
        dispatch({
          type: MessagesActionTypes.GET_MESSAGES_LIST.SUCCESS,
          payload: res.data
        });
      })
      .catch(err => {
        dispatch({
          type: MessagesActionTypes.GET_MESSAGES_LIST.ERROR,
          payload: 'error text'
        });
      });
  };
};

我试着为这个动作写了一个测试:

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

describe('Messages actions', () => {
  afterEach(() => {
    fetchMock.restore();
  });

  it('GetChatList', () => {
    fetchMock.get(config.apiUrl + config.methods.getMessagesList, { params: { accountType: 1 } });

    const expectedActions = [
      { type: MessagesActionTypes.GET_MESSAGES_LIST.REQUEST },
      {
        type: MessagesActionTypes.GET_MESSAGES_LIST.SUCCESS,
        payload: ...somePayload
      },
      {
        type: MessagesActionTypes.GET_MESSAGES_LIST.ERROR,
        payload: 'error text'
      }
    ];

    const store = mockStore({...initialState});
    return store.dispatch(GetChatList(1)).then(() => expect(store.getActions()).toEqual(expectedActions));
  });
});

然后我得到一个错误:TypeError: Cannot read 属性 'then' of undefined 为什么会发生这种情况以及如何正确测试此操作?我的错误是什么?

  • fetch-mock 模拟使用 fetch 发出的 HTTP 请求。但是你正在使用 axios.

  • 你应该 return 在 thunk 中由 axios.get() 创建的承诺。这样你就可以调用store.dispatch(GetChatList(1)).then()方法。

  • 您可以使用 jest.spyOn(axios, 'get') 模拟 axios.get() 方法及其 resolved/rejected 值。

例如

thunk.ts:

import axios from 'axios';

export const MessagesActionTypes = {
  GET_MESSAGES_LIST: {
    REQUEST: 'REQUEST',
    SUCCESS: 'SUCCESS',
    ERROR: 'ERROR',
  },
};
const config = {
  apiUrl: 'http://localhost:8080/v1/api',
  methods: {
    getMessagesList: '/messages',
  },
};
export const GetChatList = (userStatus) => {
  return (dispatch) => {
    dispatch({ type: MessagesActionTypes.GET_MESSAGES_LIST.REQUEST, payload: {} });
    return axios
      .get(config.apiUrl + config.methods.getMessagesList, { params: { accountType: userStatus } })
      .then((res) => dispatch({ type: MessagesActionTypes.GET_MESSAGES_LIST.SUCCESS, payload: res.data }))
      .catch((err) => dispatch({ type: MessagesActionTypes.GET_MESSAGES_LIST.ERROR, payload: 'error text' }));
  };
};

thunk.test.ts:

import { GetChatList, MessagesActionTypes } from './thunk';
import configureStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import { AnyAction } from 'redux';
import axios from 'axios';

interface AppState {}
type DispatchExts = ThunkDispatch<AppState, void, AnyAction>;
const mws = [thunk];
const mockStore = configureStore<AppState, DispatchExts>(mws);

describe('71296970', () => {
  afterEach(() => {
    jest.restoreAllMocks();
  });
  test('should pass', () => {
    jest.spyOn(axios, 'get').mockResolvedValue({ data: 'fake data' });
    const store = mockStore({});
    const expectedActions = [
      { type: MessagesActionTypes.GET_MESSAGES_LIST.REQUEST, payload: {} },
      { type: MessagesActionTypes.GET_MESSAGES_LIST.SUCCESS, payload: 'fake data' },
    ];

    return store.dispatch(GetChatList(1)).then(() => {
      const actions = store.getActions();
      expect(actions).toEqual(expectedActions);
    });
  });
});

测试结果:

 PASS  Whosebug/71296970/thunk.test.ts
  71296970
    ✓ should pass (5 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 thunk.ts |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.383 s