简短回答:模拟它。 =)
长答案:我个人更喜欢在测试中尽可能多地使用真实代码(即没有测试双打)。但有时它不值得,而你必须回头嘲笑。
,活像一个情况下,你描述的也有,你可能想/几件事情需要在你的测试,以检查:
- 这部分子的thunk从您的thunk下实际测试调度。
- 被测试的thunk正确地处理它发送的sub-thunk的结果。
- 被测试的thunk正确处理更新的状态,该状态由调度的子thunk更新。
根据您想要测试的上面哪种组合,您可能会使用不同的策略。例如,在被测试的thunk依赖于sub-thunk的结果的情况下,不需要检查sub-thunk是否被分派:只是模拟sub-thunk,以便返回特定的数据,这会影响特定测试中的thunk的行为,明智的方式(请参阅auth
模拟以下摘录中的细节)。
让我们考虑下面的例子来说明可能的模拟策略。想象一下你必须实现授权功能。假设您的服务器通过http端点授权用户,并且在成功的情况下,将授权令牌发回,稍后用于打开websocket连接。假设您按照以下方式设计了该功能:
有connect
thunk,它将用户的登录名和密码分配给auth
sub-thunk,然后通过http发送给定凭证。当服务器响应auth
thunk store收到的令牌store
(仅出于说明的原因)并予以解决。当auth
解决,connect
调度otherStuff
thunk这将会,以及用令牌做一些其他的东西。最后connect
通过wsApi
打开套接字连接。
// ======= connect.js =======
import { auth, getToken } from './auth';
import * as wsApi from './ws';
import { otherStuff } from './other-stuff';
export const connect = (login, password) => (dispatch, getState) => {
// ...
return dispatch(auth(login, password))
.then(() => {
const token = getToken(getState());
dispatch(otherStuff(token));
wsApi.connect(token);
});
// ...
};
// ======= auth.js =======
import * as httpApi from './http';
const saveToken = token => ({ type: 'auth/save-token', payload: token });
export const auth = (login, password) =>
dispatch =>
httpApi.login(login, password)
.then(token => dispatch(saveToken(token)));
export const getToken = state => state.auth.token;
export default (state = {}, action) => action.type === 'auth/save-token' ? { token: action.payload } : state;
// ======= other-stuff.js =======
export const otherStuff = token => (dispatch) => {
// ...
};
我们要做什么是嘲笑2层的thunk:auth
和otherStuff
。 connect
高度依赖于auth
,因此我们将确保auth
仅由检查connect
行为调用,具体取决于我们传递给auth
的模拟行为。 otherStuff
的情况稍微复杂一些。没有办法检查它实际上是否派发,然后实现自定义中间件,这将记录所有调度的操作。总而言之测试将如下所示(我使用jest的嘲笑):
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import { connect } from './connect';
import { auth, getToken } from './auth';
import { otherStuff } from './other-stuff';
import * as wsApi from './ws';
const authReducer = require.requireActual('./auth').default;
jest.mock('./auth');
jest.mock('./ws');
jest.mock('./other-stuff');
const makeSpyMiddleware =() => {
const dispatch = jest.fn();
return {
dispatch,
middleware: store => next => action => {
dispatch(action);
return next(action);
}
};
};
describe('connect',() => {
let store;
let spy;
beforeEach(() => {
jest.clearAllMocks();
spy = makeSpyMiddleware();
store = createStore(authReducer, {}, applyMiddleware(spy.middleware, thunk));
auth.mockImplementation((login, password) =>() => {
if (login === 'user' && password == 'password') return Promise.resolve();
return Promise.reject();
});
});
test('happy path',() => {
getToken.mockImplementation(() => 'generated token');
otherStuff.mockImplementation(token => ({ type: 'mocked/other-stuff', token }));
return store.dispatch(connect('user', 'password')).then(() => {
expect(wsApi.connect).toHaveBeenCalledWith('generated token');
expect(spy.dispatch).toHaveBeenCalledWith({ type: 'mocked/other-stuff', token: 'generated token'});
});
});
test('auth failed',() => {
return store.dispatch(connect('user', 'wrong-password')).catch(() => {
expect(wsApi.connect).not.toHaveBeenCalled();
});
});
});
如果您需要在给定的片段任何意见,随意问。
不确定它是否可以帮助您处理所有用例,但您是否考虑过使用middleWares? –