如何对异步 Redux 操作进行单元测试以模拟 ajax 响应
How to unit test async Redux actions to mock ajax response
我正在创建一个中间件,用于使用异步操作发出 ajax 请求。中间件拦截原始操作,执行 ajax 请求,并重新 dispatch
原始操作以及来自 url
.
的响应
所以,我的组件只会dispatch
这样的动作
onClick() {
dispatch(ActionCreator.fetchUser());
}
其余部分将由中间件处理 here。
我的问题是,单元测试应该怎么做?我应该嘲笑 onClick
本身吗?或者我应该编写一个模拟中间件并使用模拟响应转发操作?
我不确定应该采用哪种方法。我尝试了 several stuff,但我尝试的 none 对我来说是有意义的。
有什么指点吗?
事实证明,我不需要模拟任何存储方法或任何东西。它就像模拟 ajax 请求一样简单。我正在使用 superagent
,所以我这样做了
const mockResponse = {
body: {
data: 'something'
}
};
spyOn(superagent.Request.prototype, 'end').and.callFake((cb) => {
cb(null, mockResponse); // callback with mocked response
});
// and expect it to be called
expect(superagent.Request.prototype.end).toHaveBeenCalled();
注意:下面的回答有点过时了。
描述了一种更简单的更新方法 here。
不过,你仍然可以用另一种方式来做。
我们现在在官方文档中有 a section on testing async action creators。
对于使用 Redux Thunk or other middleware, it’s best to completely mock the Redux store for tests. You can still use applyMiddleware()
with a mock store, as shown below. You can also use nock 模拟 HTTP 请求的异步操作创建者。
function fetchTodosRequest() {
return {
type: ADD_TODOS_REQUEST
};
}
function fetchTodosSuccess(body) {
return {
type: ADD_TODOS_SUCCESS,
body
};
}
function fetchTodosFailure(ex) {
return {
type: ADD_TODOS_FAILURE,
ex
};
}
export function fetchTodos(data) {
return dispatch => {
dispatch(fetchTodosRequest());
return fetch('http://example.com/todos')
.then(res => res.json())
.then(json => dispatch(addTodosSuccess(json.body)))
.catch(ex => dispatch(addTodosFailure(ex)));
};
}
可以这样测试:
import expect from 'expect';
import { applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import * as actions from '../../actions/counter';
import * as types from '../../constants/ActionTypes';
import nock from 'nock';
const middlewares = [thunk];
/**
* Creates a mock of Redux store with middleware.
*/
function mockStore(getState, expectedActions, onLastAction) {
if (!Array.isArray(expectedActions)) {
throw new Error('expectedActions should be an array of expected actions.');
}
if (typeof onLastAction !== 'undefined' && typeof onLastAction !== 'function') {
throw new Error('onLastAction should either be undefined or function.');
}
function mockStoreWithoutMiddleware() {
return {
getState() {
return typeof getState === 'function' ?
getState() :
getState;
},
dispatch(action) {
const expectedAction = expectedActions.shift();
expect(action).toEqual(expectedAction);
if (onLastAction && !expectedActions.length) {
onLastAction();
}
return action;
}
}
}
const mockStoreWithMiddleware = applyMiddleware(
...middlewares
)(mockStoreWithoutMiddleware);
return mockStoreWithMiddleware();
}
describe('async actions', () => {
afterEach(() => {
nock.cleanAll();
});
it('creates FETCH_TODO_SUCCESS when fetching todos has been done', (done) => {
nock('http://example.com/')
.get('/todos')
.reply(200, { todos: ['do something'] });
const expectedActions = [
{ type: types.FETCH_TODO_REQUEST },
{ type: types.FETCH_TODO_SUCCESS, body: { todos: ['do something'] } }
]
const store = mockStore({ todos: [] }, expectedActions, done);
store.dispatch(actions.fetchTodos());
});
});
我正在创建一个中间件,用于使用异步操作发出 ajax 请求。中间件拦截原始操作,执行 ajax 请求,并重新 dispatch
原始操作以及来自 url
.
所以,我的组件只会dispatch
这样的动作
onClick() {
dispatch(ActionCreator.fetchUser());
}
其余部分将由中间件处理 here。
我的问题是,单元测试应该怎么做?我应该嘲笑 onClick
本身吗?或者我应该编写一个模拟中间件并使用模拟响应转发操作?
我不确定应该采用哪种方法。我尝试了 several stuff,但我尝试的 none 对我来说是有意义的。
有什么指点吗?
事实证明,我不需要模拟任何存储方法或任何东西。它就像模拟 ajax 请求一样简单。我正在使用 superagent
,所以我这样做了
const mockResponse = {
body: {
data: 'something'
}
};
spyOn(superagent.Request.prototype, 'end').and.callFake((cb) => {
cb(null, mockResponse); // callback with mocked response
});
// and expect it to be called
expect(superagent.Request.prototype.end).toHaveBeenCalled();
注意:下面的回答有点过时了。
描述了一种更简单的更新方法 here。
不过,你仍然可以用另一种方式来做。
我们现在在官方文档中有 a section on testing async action creators。
对于使用 Redux Thunk or other middleware, it’s best to completely mock the Redux store for tests. You can still use applyMiddleware()
with a mock store, as shown below. You can also use nock 模拟 HTTP 请求的异步操作创建者。
function fetchTodosRequest() {
return {
type: ADD_TODOS_REQUEST
};
}
function fetchTodosSuccess(body) {
return {
type: ADD_TODOS_SUCCESS,
body
};
}
function fetchTodosFailure(ex) {
return {
type: ADD_TODOS_FAILURE,
ex
};
}
export function fetchTodos(data) {
return dispatch => {
dispatch(fetchTodosRequest());
return fetch('http://example.com/todos')
.then(res => res.json())
.then(json => dispatch(addTodosSuccess(json.body)))
.catch(ex => dispatch(addTodosFailure(ex)));
};
}
可以这样测试:
import expect from 'expect';
import { applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import * as actions from '../../actions/counter';
import * as types from '../../constants/ActionTypes';
import nock from 'nock';
const middlewares = [thunk];
/**
* Creates a mock of Redux store with middleware.
*/
function mockStore(getState, expectedActions, onLastAction) {
if (!Array.isArray(expectedActions)) {
throw new Error('expectedActions should be an array of expected actions.');
}
if (typeof onLastAction !== 'undefined' && typeof onLastAction !== 'function') {
throw new Error('onLastAction should either be undefined or function.');
}
function mockStoreWithoutMiddleware() {
return {
getState() {
return typeof getState === 'function' ?
getState() :
getState;
},
dispatch(action) {
const expectedAction = expectedActions.shift();
expect(action).toEqual(expectedAction);
if (onLastAction && !expectedActions.length) {
onLastAction();
}
return action;
}
}
}
const mockStoreWithMiddleware = applyMiddleware(
...middlewares
)(mockStoreWithoutMiddleware);
return mockStoreWithMiddleware();
}
describe('async actions', () => {
afterEach(() => {
nock.cleanAll();
});
it('creates FETCH_TODO_SUCCESS when fetching todos has been done', (done) => {
nock('http://example.com/')
.get('/todos')
.reply(200, { todos: ['do something'] });
const expectedActions = [
{ type: types.FETCH_TODO_REQUEST },
{ type: types.FETCH_TODO_SUCCESS, body: { todos: ['do something'] } }
]
const store = mockStore({ todos: [] }, expectedActions, done);
store.dispatch(actions.fetchTodos());
});
});