在 redux 动作测试中出错

err in a redux action test

我是 Jest 测试和 moxios 的新手。只是想写我的第一个异步动作测试。测试死于此错误:

Expected value to equal:
  [{"payload": {"checked": true, "followingInfoId": "1"}, "type": "HANDLE_FAVORITE_SUCCESS"}]
Received:
  [{"payload": [TypeError: Cannot read property 'getItem' of undefined], "type": "ERROR"}]

谁能告诉我问题出在哪里。我想 moxios 响应不会转到 "then"?

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import moxios from 'moxios';
import * as actions from './index';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const store = mockStore();

describe('followings actions', () => {
  beforeEach(() => {
    moxios.install();
    store.clearActions();
  });

  afterEach(() => {
    moxios.uninstall();
  });

  it('dispatches the HANDLE_FAVORITE_SUCCESS action', () => {
    moxios.wait(() => {
      const request = moxios.requests.mostRecent();
      request.respondWith({
        status: 200,
        payload: {
          followingInfoId: '1',
          checked: true
        }
      });
    });

    const expectedActions = [
      {
        'type': 'HANDLE_FAVORITE_SUCCESS',
        payload: {
          followingInfoId: '1',
          checked: true
        }
      }
    ];

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

这是动作创建者:

export const handleFavorite = data => {
  return dispatch => {
    return followApi.handleFavorite(data).then(payload => {
      dispatch({ type: 'HANDLE_FAVORITE_SUCCESS', payload });
    }, err => {    
      dispatch({ type: 'ERROR', payload: err })
    });
  }
};

这里是 followApi.handleFavorite:

handleFavorite: (data) => {
    return new Promise ((resolve, reject) => {
      httpServise.patch(`${host}:${port}/followings/handle-favorite`, data).then(
        res => {
          if (res.data.payload) {
            resolve(res.data.payload);
          } else reject({status: 401});
        }, err => reject(err)
      );
    });
  },

如果需要,还有 http 服务的一部分:

patch: (url, params) => {
  return new Promise((resolve, reject) => {
      axios(url, {
          method: 'PATCH',
          headers: getHeaders(),
          data: params
      }).then(res => {
          resolve(res);
      }, err => {
          reject(err);
      });
  });
}

如果你想测试动作创建者,你应该模拟 followApi.handleFavorite 方法而不是 axios

这里是测试动作创建者只使用jestjstypescript的解决方案,您可以自己手动模拟模块。

文件夹结构:

.
├── actionCreators.spec.ts
├── actionCreators.ts
├── followApi.ts
└── httpServise.ts

actionCreators.ts:

import followApi from './followApi';

export const handleFavorite = data => {
  return dispatch => {
    return followApi.handleFavorite(data).then(
      payload => {
        dispatch({ type: 'HANDLE_FAVORITE_SUCCESS', payload });
      },
      err => {
        dispatch({ type: 'ERROR', payload: err });
      }
    );
  };
};

followApi.ts:

import { httpServise } from './httpServise';

const host = 'http://github.com/mrdulin';
const port = 3000;

const followApi = {
  handleFavorite: data => {
    return new Promise((resolve, reject) => {
      httpServise.patch(`${host}:${port}/followings/handle-favorite`, data).then(
        (res: any) => {
          if (res.data.payload) {
            resolve(res.data.payload);
          } else {
            reject({ status: 401 });
          }
        },
        err => reject(err)
      );
    });
  }
};

export default followApi;

httpService.ts:

import axios from 'axios';

function getHeaders() {
  return {};
}

export const httpServise = {
  patch: (url, params) => {
    return new Promise((resolve, reject) => {
      axios(url, {
        method: 'PATCH',
        headers: getHeaders(),
        data: params
      }).then(
        res => {
          resolve(res);
        },
        err => {
          reject(err);
        }
      );
    });
  }
};

actionCreators.spec.ts:

import configureMockStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import { AnyAction } from 'redux';
import * as actions from './actionCreators';
import followApi from './followApi';

jest.mock('./followApi.ts', () => {
  return {
    handleFavorite: jest.fn()
  };
});

type State = any;
const middlewares = [thunk];
const mockStore = configureMockStore<State, ThunkDispatch<State, undefined, AnyAction>>(middlewares);
const store = mockStore();

describe('followings actions', () => {
  beforeEach(() => {
    store.clearActions();
    jest.resetAllMocks();
  });
  it('dispatches the HANDLE_FAVORITE_SUCCESS action', () => {
    expect.assertions(2);
    const mockedHandleFavoritePayload = {
      followingInfoId: '1',
      checked: true
    };
    (followApi.handleFavorite as jest.MockedFunction<typeof followApi.handleFavorite>).mockResolvedValueOnce(
      mockedHandleFavoritePayload
    );
    const data = 'jest';
    const expectedActions = [
      {
        type: 'HANDLE_FAVORITE_SUCCESS',
        payload: {
          followingInfoId: '1',
          checked: true
        }
      }
    ];
    return store.dispatch(actions.handleFavorite(data)).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
      expect(followApi.handleFavorite).toBeCalledWith(data);
    });
  });

  it('dispatches the ERROR action', () => {
    const mockedhHndleFavoriteError = new Error('network error');
    (followApi.handleFavorite as jest.MockedFunction<typeof followApi.handleFavorite>).mockRejectedValueOnce(
      mockedhHndleFavoriteError
    );
    const data = 'jest';
    const expectedActions = [
      {
        type: 'ERROR',
        payload: mockedhHndleFavoriteError
      }
    ];
    return store.dispatch(actions.handleFavorite(data)).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
      expect(followApi.handleFavorite).toBeCalledWith(data);
    });
  });
});

具有 100% 覆盖率报告的单元测试结果:

PASS  src/Whosebug/52025257/actionCreators.spec.ts (5.95s)
  followings actions
    ✓ dispatches the HANDLE_FAVORITE_SUCCESS action (5ms)
    ✓ dispatches the ERROR action (2ms)

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

源代码:https://github.com/mrdulin/jest-codelab/tree/master/src/Whosebug/52025257