redux-sagas中Firebase Auth依赖注入的测试匹配失败问题排查
解决Redux Saga测试中CALL IO对象匹配失败的问题
我太懂你这种头疼的感觉了——明明代码跑起来完全正常,测试里的CALL语句看起来和预期一模一样,但就是匹配失败,折腾一整天都没头绪对吧?别着急,咱们来拆解问题根源,一步步解决它。
问题根源:函数引用与调用上下文的一致性问题
你在saga里创建了一个内部的wrapper对象,里面的authFunction是绑定到这个wrapper的方法;但测试里你单独搞了个authMock的authFunction,虽然逻辑上返回的是同一个currentUser,但这两个authFunction是完全不同的函数引用,而且调用时的上下文对象(call的第一个参数)也和saga里的wrapper不一致。Redux Saga的call匹配是严格校验引用的,所以哪怕视觉上一样,也会判定不匹配。
解决方案1:对齐测试与saga的内部结构
直接模拟saga里的wrapper结构,确保测试里的调用上下文和函数引用和saga内部完全一致:
describe('should test isAuth', () => { const auth = { currentUser: { id: 'test-user' } }; // 完全复刻saga内部的wrapper结构 const wrapper = { authFunction: () => auth.currentUser }; const it = sagaHelper(isAuth(auth)); // 这里传原始auth,不是自定义的authMock it('should call the internal authFunction', result => { // 匹配的参数要和saga里的call完全一致:[wrapper, wrapper.authFunction] expect(result).toEqual(call([wrapper, wrapper.authFunction])); return { user: auth.currentUser }; }); it('should dispatch authSuccess with user data', result => { expect(result).toEqual(put(ActionCreator.authSuccess(auth.currentUser))); }); });
解决方案2:简化saga代码,去掉不必要的wrapper
其实你saga里的wrapper完全是多余的,直接简化代码后测试会更清晰:
// 简化后的saga代码 export function* isAuth(auth) { try { const user = auth.currentUser; if (user !== null) { yield put(ActionCreator.authSuccess(user)); } } catch ({ message }) { yield put(ActionCreator.authFailure(message)); } }
对应的测试也能大幅简化:
describe('should test isAuth', () => { const mockUser = { id: '123' }; it('should dispatch authSuccess when user exists', () => { const auth = { currentUser: mockUser }; const it = sagaHelper(isAuth(auth)); it('should put authSuccess action', result => { expect(result).toEqual(put(ActionCreator.authSuccess(mockUser))); }); }); it('should not dispatch authSuccess when user is null', () => { const auth = { currentUser: null }; const it = sagaHelper(isAuth(auth)); it('should finish without dispatching any action', result => { expect(result).toBeUndefined(); }); }); });
解决方案3:用Mock函数忽略引用差异(如果必须保留wrapper)
如果你因为业务需求必须保留saga里的wrapper结构,可以用jest的Mock函数来绕过引用校验:
// 调整saga,让authFunction可以被Mock(或者直接用箭头函数避免上下文问题) export function* isAuth(auth) { try { const wrapper = { authFunction: () => auth.currentUser }; // 箭头函数不需要绑定上下文,直接调用即可 const user = yield call(wrapper.authFunction); if (user !== null) { yield put(ActionCreator.authSuccess(user)); } } catch ({ message }) { yield put(ActionCreator.authFailure(message)); } }
测试时用expect.any(Function)来匹配函数类型,不用纠结具体引用:
describe('should test isAuth', () => { const mockUser = { id: 'test' }; const auth = { currentUser: mockUser }; const it = sagaHelper(isAuth(auth)); it('should call the authFunction', result => { expect(result).toEqual(call(expect.any(Function))); return { user: mockUser }; }); it('should dispatch authSuccess', result => { expect(result).toEqual(put(ActionCreator.authSuccess(mockUser))); }); });
内容的提问来源于stack exchange,提问作者jose dos santos
相关产品推荐
相关产品推荐

