redux-thunk结构和测试副作用

时间:2017-11-29 19:15:04

标签: javascript reactjs redux jestjs redux-thunk

我正在使用redux-thunk而不确定副作用(showAlertError函数)是否结构正确。虽然我的初学测试设置乍一看似乎很好,但我得到jest.fn() value must be a mock function or spy. Received: undefined错误。

showAlertError函数是在正确的位置还是应该在actionCreator中,或者在其他地方?此外,如果这是正确的地方,那么我如何测试它是否被调用。

export const submitTeammateInvitation = (data) => {
  const config = {
   // config code
  };

  return async (dispatch) => {
    dispatch(submitTeammateInvitationRequest(data));

    try {
      const response = await fetch(inviteTeammateEndpoint, config);
      const jsonResponse = await response.json();
      if (!response.ok) {
        showErrorAlert(jsonResponse);
        dispatch(submitTeammateInvitationError(jsonResponse));

        throw new Error(response.statusText);
      }

      dispatch(submitTeammateInvitationSuccess(jsonResponse));
    } catch (error) {
      if (process.env.NODE_ENV === 'development') {
        console.log('Request failed', error);
      }
    }
  };
};

测试

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';

import { showAlertError } from '../../../../_helpers/alerts';
jest.mock('../../../../_helpers/alerts');

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

describe('submitTeammateInvitation', () => {
   it('dispatches the correct actions on a failed fetch request', () => {
     fetch.mockResponse(
      JSON.stringify(error),
      { status: 500, statusText: 'Internal Server Error' }
    );

    const store = createMockStore({});
    const expectedActions = [
      submitTeammateInvitationRequestObject,
      submitTeammateInvitationErrorObject
    ];
    const showAlertError = jest.fn();

    return store.dispatch(submitTeammateInvitation(inviteTeammateEndpoint))
      .then(() => {
        expect(showAlertError).toBeCalled(); // this doesn't work
        expect(store.getActions()).toEqual(expectedActions); // this works
      });
  });
});

1 个答案:

答案 0 :(得分:0)

您可以手动模拟showErrorAlert函数。解决方法如下:

actionCreators.ts

import fetch from 'node-fetch';
import { showErrorAlert } from './showErrorAlert';

const SUBMIT_TEAMATE_INVITATION_REQUEST = 'SUBMIT_TEAMATE_INVITATION_REQUEST';
const SUBMIT_TEAMATE_INVITATION_SUCCESS = 'SUBMIT_TEAMATE_INVITATION_SUCCESS';
const SUBMIT_TEAMATE_INVITATION_ERROR = 'SUBMIT_TEAMATE_INVITATION_ERROR';

export const submitTeammateInvitationRequest = data => ({ type: SUBMIT_TEAMATE_INVITATION_REQUEST, payload: { data } });
export const submitTeammateInvitationSuccess = data => ({ type: SUBMIT_TEAMATE_INVITATION_SUCCESS, payload: { data } });
export const submitTeammateInvitationError = data => ({ type: SUBMIT_TEAMATE_INVITATION_ERROR, payload: { data } });

export const submitTeammateInvitation = data => {
  const config = {
    // config code
  };

  const inviteTeammateEndpoint = 'https://github.com/mrdulin';

  return async dispatch => {
    dispatch(submitTeammateInvitationRequest(data));

    try {
      const response = await fetch(inviteTeammateEndpoint, config);
      const jsonResponse = await response.json();
      if (!response.ok) {
        showErrorAlert(jsonResponse);
        dispatch(submitTeammateInvitationError(jsonResponse));

        throw new Error(response.statusText);
      }

      dispatch(submitTeammateInvitationSuccess(jsonResponse));
    } catch (error) {
      if (process.env.NODE_ENV === 'development') {
        console.log('Request failed', error);
      }
    }
  };
};

showErrorAlert.ts

export function showErrorAlert(jsonResponse) {
  console.log(jsonResponse);
}

actionCreators.spec.ts

import {
  submitTeammateInvitation,
  submitTeammateInvitationRequest,
  submitTeammateInvitationSuccess,
  submitTeammateInvitationError
} from './actionCreators';
import createMockStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import fetch from 'node-fetch';
import { AnyAction } from 'redux';
import { showErrorAlert } from './showErrorAlert';

const { Response } = jest.requireActual('node-fetch');

jest.mock('node-fetch');
jest.mock('./showErrorAlert.ts', () => {
  return {
    showErrorAlert: jest.fn()
  };
});

const middlewares = [thunk];
const mockStore = createMockStore<any, ThunkDispatch<any, any, AnyAction>>(middlewares);

describe('submitTeammateInvitation', () => {
  it('dispatches the correct actions on a failed fetch request', () => {
    const mockedResponse = { data: 'mocked response' };
    const mockedJSONResponse = JSON.stringify(mockedResponse);
    const mockedData = { data: 'mocked data' };
    (fetch as jest.MockedFunction<typeof fetch>).mockResolvedValueOnce(
      new Response(mockedJSONResponse, { status: 500, statusText: 'Internal Server Error' })
    );

    const intialState = {};
    const store = mockStore(intialState);
    const expectedActions = [
      submitTeammateInvitationRequest(mockedData),
      submitTeammateInvitationError(mockedResponse)
    ];
    return store.dispatch(submitTeammateInvitation(mockedData)).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
      expect(showErrorAlert).toBeCalledWith(mockedResponse);
    });
  });
});

带有覆盖率报告的单元测试结果:

 PASS  src/stackoverflow/47560126/actionCreators.spec.ts
  submitTeammateInvitation
    ✓ dispatches the correct actions on a failed fetch request (11ms)

-------------------|----------|----------|----------|----------|-------------------|
File               |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files          |    89.29 |       50 |    83.33 |    90.91 |                   |
 actionCreators.ts |    89.29 |       50 |    83.33 |    90.91 |             32,35 |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        5.864s

以下是完整的演示:https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/47560126