无法为动作创建者编写 Redux 测试

Unable to write Redux tests for action creators

原始问题

我正在按照 Redux documentation 中详细说明的为异步操作创建者编写测试的示例进行操作。我尽可能地按照示例进行操作,但我无法让测试正常工作。我收到以下错误消息:

   TypeError: Cannot read property 'then' of undefined
   (node:789) UnhandledPromiseRejectionWarning: Unhandled promise rejection 
   (rejection id: 28): TypeError: Cannot read property 'data' of undefined

这是我的动作创建者和测试的代码:

actions/index.js

import axios from 'axios';
import { browserHistory } from 'react-router';
import { AUTH_USER, AUTH_ERROR, RESET_AUTH_ERROR } from './types';

const API_HOST = process.env.NODE_ENV == 'production'
                ? http://production-server
                : 'http://localhost:3090';

export function activateUser(token) {
  return function(dispatch) {
    axios.put(`${API_HOST}/activations/${token}`)
      .then(response => {
        dispatch({ type: AUTH_USER });

        localStorage.setItem('token', response.data.token);
      })
      .catch(error => {
        dispatch(authError(error.response.data.error));
      });
  }
}

export function authError(error) {
  return {
    type: AUTH_ERROR,
    payload: error
  }
}

confirmation_test.js

import configureMockStore from 'redux-mock-store'; 
import thunk from 'redux-thunk';
import * as actions from '../../src/actions';
import { AUTH_USER, AUTH_ERROR, RESET_AUTH_ERROR } from 
'../../src/actions/types';
import nock from 'nock';
import { expect } from 'chai';

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

describe('Confirmation_Token action creator', () => {
  afterEach(() => {
    nock.cleanAll()
  });

  it('dispatches AUTH_USER', (done) => {
    nock('http://localhost:3090')
    .put('/activations/123456')
    .reply(200, {
        token: 7891011
    });

    const expectedActions = { type: AUTH_USER };

    const store = mockStore({});

    return store.dispatch(actions.activateUser(123456))
     .then(() => { // return of async actions
       expect(store.getActions()).toEqual(expectedActions);
       done();
     });
  });
});

更新问题

我已经部分(虽然不是全部)解决了这个问题。我通过在 axios 前面添加 return 语句并注释掉 localstorage.setItem 调用来实现这一点。

我也把我分配给expectedActions的对象变成了一个数组,并且把我的断言从toEqual改成了to.deep.equal。这是修改后的代码:

actions/index.js

export function activateUser(token) {
  return function(dispatch) { // added return statement
    return axios.put(`${API_HOST}/activations/${token}`)
      .then(response => {
        dispatch({ type: AUTH_USER });
        // localStorage.setItem('token', response.data.token); Had to comment out local storage
      })
      .catch(error => {
        dispatch(authError(error.response.data.error));
      });
  }
}

confirmation_test.js

describe('ConfirmationToken action creator', () => {
  afterEach(() => {
     nock.cleanAll()
  });

  it('dispatches AUTH_USER', (done) => {
    nock('http://localhost:3090')
    .put('/activations/123456')
    .reply(200, {
        token: 7891011
     });

    const expectedActions = [{ type: AUTH_USER }];

    const store = mockStore({});

    return store.dispatch(actions.activateUser(123456))
     .then(() => { // return of async actions
       expect(store.getActions()).to.deep.equal(expectedActions);
       done();
     });
   });
 });

但现在我无法测试 localStorage.setItem 而不产生此错误消息:

Error: timeout of 2000ms exceeded. Ensure the done() callback is being called 
in this test.

这是因为我需要模拟localStorage.setItem吗?还是我缺少更简单的解决方案?

我想出了解决办法。它涉及我在更新的问题中所做的更改,以及在我的 test_helper.js 文件中添加 localStorage 的模拟。由于网上似乎有很多关于此的问题,我想也许我的解决方案可以帮助某人。

test_helper.js

import jsdom from 'jsdom';

global.localStorage = storageMock();

global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = global.document.defaultView;
global.navigator = global.window.navigator;

global.window.localStorage = global.localStorage;

// localStorage mock

function storageMock() {
    var storage = {};

    return {
      setItem: function(key, value) {
        storage[key] = value || '';
      },
      getItem: function(key) {
        return key in storage ? storage[key] : null;
      },
      removeItem: function(key) {
        delete storage[key];
      }
    };
  }

actions.index.js

export function activateUser(token) {
  return function(dispatch) {
    return axios.put(`${API_HOST}/activations/${token}`)
      .then(response => {
        dispatch({ type: AUTH_USER });
        localStorage.setItem('token', response.data.token);
      })
      .catch(error => {
        dispatch(authError(error.response.data.error));
      });
  }
}

confirmation_test.js

describe('Confirmation action creator', () => {
  afterEach(() => {
    nock.cleanAll()
  });

  it('dispatches AUTH_USER and stores token in localStorage', (done) => {
    nock('http://localhost:3090')
    .put('/activations/123456')
    .reply(200, {
        token: '7891011'
    });

    const expectedActions = [{ type: AUTH_USER }];

    const store = mockStore({});

    return store.dispatch(actions.activateUser(123456))
     .then(() => { // return of async actions
       expect(store.getActions()).to.deep.equal(expectedActions);
       expect(localStorage.getItem('token')).to.equal('7891011');
       done();
    });
  });
});