无法为动作创建者编写 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();
});
});
});
原始问题
我正在按照 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();
});
});
});