如何使用Jest测试依赖RNFirebase的SignIn Redux Saga?
解决Redux Saga测试中RNFirebase依赖报错的问题
这个问题很典型——在测试依赖原生模块的代码时,我们没法在Node.js测试环境中加载真实的RNFirebase,所以得通过Mock或者依赖注入来绕开原生依赖的限制。下面给你两种可行的解决方案:
方法一:直接Mock RNFirebase模块
利用Jest的模块Mock功能,在测试文件中模拟RNFirebase的firestore()和相关方法,让AccountRepo在测试时使用Mock的firebase实例,而不是真实的原生模块。
修改你的AccountSaga.test.js文件如下:
import { signIn } from '../../app/saga/AccountSagas'; import { call } from 'redux-saga/effects'; import { SIGN_IN, SHOW_SIGN_UP, SIGNED_IN, ERROR } from '../../app/action/types'; import { AccountRepo } from '../../app/repo'; // 提前Mock RNFirebase模块 jest.mock('react-native-firebase', () => { // 模拟firestore的get方法返回存在的文档 const mockDoc = { get: jest.fn().mockResolvedValue({ exists: true, data: jest.fn().mockReturnValue({ id: 'DFDDGDG', name: 'Test User' }) }) }; const mockCollection = { doc: jest.fn().mockReturnValue(mockDoc) }; return { firestore: jest.fn().mockReturnValue({ collection: jest.fn().mockReturnValue(mockCollection) }) }; }); describe('>>> signIn', () => { const mockUser = { uid: 'DFDDGDG' }; const action = { type: SIGN_IN, payload: { user: mockUser } }; const generator = signIn(action); it('+++ must call AccountRepo.getAccount', () => { const next = generator.next().value; expect(next).toEqual(call(AccountRepo.getAccount, mockUser)); }); it('+++ should dispatch SIGNED_IN when account exists', () => { // 先执行getAccount的call generator.next(); // 然后获取put的动作 const next = generator.next({ id: 'DFDDGDG', name: 'Test User' }).value; expect(next).toEqual({ type: SIGNED_IN, payload: { id: 'DFDDGDG', name: 'Test User' } }); }); it('+++ should dispatch SHOW_SIGN_UP when account does not exist', () => { // 重新生成generator,这次Mock文档不存在 jest.mock('react-native-firebase', () => { const mockDoc = { get: jest.fn().mockResolvedValue({ exists: false }) }; const mockCollection = { doc: jest.fn().mockReturnValue(mockDoc) }; return { firestore: jest.fn().mockReturnValue({ collection: jest.fn().mockReturnValue(mockCollection) }) }; }); const generator = signIn(action); generator.next(); const next = generator.next(null).value; expect(next).toEqual({ type: SHOW_SIGN_UP, payload: mockUser }); }); });
方法二:重构AccountRepo使用依赖注入(更推荐)
这种方法让代码的可测试性更强,把firebase实例作为依赖传入AccountRepo,而不是在模块内直接导入,这样测试时可以轻松替换成Mock对象。
第一步:重构AccountRepo.js
// 把firebase作为参数传入,默认使用真实的react-native-firebase import firebase from 'react-native-firebase'; import { ACCOUNT_PATH } from './constants'; export const createAccountRepo = (firebaseInstance = firebase) => { const ACCOUNT_REF = firebaseInstance.firestore().collection(ACCOUNT_PATH); return { getAccount(user){ console.log('Getting account for user :'+user.uid); return ACCOUNT_REF.doc(user.uid).get().then(function(doc) { if (doc.exists) { return doc.data(); } else { console.warn("No such document!"); return null; } }) } } }; // 导出默认的Repo实例,供业务代码使用 export const AccountRepo = createAccountRepo();
第二步:修改AccountSaga.js,支持注入AccountRepo
import { call, put, select , take, takeEvery} from 'redux-saga/effects'; import { SIGN_IN, SIGN_UP,SHOW_SIGN_UP, SIGNED_IN, ERROR } from '../action/types'; import { createAccountRepo } from '../repo'; // 允许传入自定义的AccountRepo,默认使用真实实例 export function* signIn(action, accountRepo = createAccountRepo()) { try{ const {user} = action.payload; console.log('In *SIGN_IN saga for user '+JSON.stringify(user)); const account = yield call(accountRepo.getAccount, user); console.log('account is '+account); if(account) { yield put({type: SIGNED_IN, payload:account}); } else { yield put({type: SHOW_SIGN_UP, payload:user}); } } catch (error) { console.log('Error retrieving account '+error); yield put({type: ERROR,error}); } } // 如果需要在Saga中间件中使用,还是用默认的Repo export const AccountSagas = [ takeEvery(SIGN_IN, signIn) ]
第三步:修改测试文件,传入Mock的AccountRepo
import { signIn } from '../../app/saga/AccountSagas'; import { call } from 'redux-saga/effects'; import { SIGN_IN, SHOW_SIGN_UP, SIGNED_IN, ERROR } from '../../app/action/types'; describe('>>> signIn', () => { const mockUser = { uid: 'DFDDGDG' }; const action = { type: SIGN_IN, payload: { user: mockUser } }; it('+++ must call AccountRepo.getAccount', () => { // 创建Mock的AccountRepo const mockAccountRepo = { getAccount: jest.fn().mockResolvedValue({ id: 'DFDDGDG', name: 'Test User' }) }; const generator = signIn(action, mockAccountRepo); const next = generator.next().value; expect(next).toEqual(call(mockAccountRepo.getAccount, mockUser)); expect(mockAccountRepo.getAccount).toHaveBeenCalledWith(mockUser); }); it('+++ should dispatch SIGNED_IN when account exists', () => { const mockAccountRepo = { getAccount: jest.fn().mockResolvedValue({ id: 'DFDDGDG', name: 'Test User' }) }; const generator = signIn(action, mockAccountRepo); generator.next(); // 执行getAccount的call const next = generator.next({ id: 'DFDDGDG', name: 'Test User' }).value; expect(next).toEqual({ type: SIGNED_IN, payload: { id: 'DFDDGDG', name: 'Test User' } }); }); it('+++ should dispatch SHOW_SIGN_UP when account does not exist', () => { const mockAccountRepo = { getAccount: jest.fn().mockResolvedValue(null) }; const generator = signIn(action, mockAccountRepo); generator.next(); // 执行getAccount的call const next = generator.next(null).value; expect(next).toEqual({ type: SHOW_SIGN_UP, payload: mockUser }); }); });
两种方法都能解决你的问题,方法二更利于长期维护,因为它让代码的依赖关系更清晰,测试也更灵活。
内容的提问来源于stack exchange,提问作者mzafer
相关产品推荐
相关产品推荐

