You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:28:42