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

如何用Jest测试React Native中基于Redux状态的导航栈切换

问题

我正在开发一款基于React Native Expo的应用,采用Redux进行状态管理、React Navigation实现导航功能。我希望编写Jest测试用例,确保根据Redux存储中的onboarded状态显示对应的正确屏幕。该状态由异步调用设置,但我已完成了其模拟实现。请问如何测试当onboarded状态设为true时,是否正确显示BottomTabNavigation而非LoginEmailScreen?


应用导航容器实现

const RootNavigationContainer = () => {
  const onboarded = useAppSelector(selectOnboarded);
  return (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{ headerShown: false }}>
        {!onboarded ? (
          <>
            <Stack.Screen
              name="OnboardingStart"
              component={OnboardingStartScreen}
            />
            <Stack.Screen
              name="LoginEmail"
              component={LoginEmailScreen}
              options={{ animation: "none" }}
            />
            <Stack.Screen
              name="SignUpEmail"
              component={SignUpEmailScreen}
              options={{ animation: "none" }}
            />
          </>
        ) : (
          <>
            <Stack.Screen
              name="BottomTabNavigation"
              component={BottomTabNavigation}
            />
            <Stack.Screen name="Settings" component={SettingsScreen} />
          </>
        )}
      </Stack.Navigator>
    </NavigationContainer>
  );
};

当前测试代码

it("should navigate to the home screen when a user is onboarded", async () => {
    const { getByTestId } = renderWithProviders(
      <LoginEmailScreen navigation={navigation as any} route={mockRoute} />
    );
    onboarded = true;
    fireEvent.changeText(getByTestId("email-input"), "test@test.de");
    fireEvent.changeText(getByTestId("password-input"), "test");
    fireEvent.press(getByTestId("login-button"));
    await waitFor(() => {
      expect(checkIfOnboarded).toHaveBeenCalled();
    });
  });

模拟实现代码

jest.mock("./script.ts", () => ({
  signInWithEmail: jest.fn().mockImplementation(async (email, password) => {
    if (email === "" || password === "") {
      return "Email or password is empty";
    }
    if (email === "test@test.de" && password === "test") {
      return;
    } else {
      return "Email or password is incorrect";
    }
  }),
  checkIfOnboarded: jest.fn().mockImplementation((userId) => {
    if (userId === "mockUserId") {
      return [
        { categories: [], name: "", username: "", savedPosts: [], avatar: "" },
      ];
    } else {
      return [];
    }
  }),
}));

let onboarded: boolean;

const getUserMock = jest.fn().mockImplementation(async () => {
  if (onboarded) {
    return { data: { user: { id: "mockUserId" } } };
  } else {
    return { data: { user: { id: "notOnboardedId" } } };
  }
});

supabase.auth.getUser = getUserMock;

LoginEmailScreen核心逻辑

const loginConfirmed = async () => {
    setSignInButtonDisabled(true);
    const errorMessage = await signInWithEmail(email.trim(), password.trim());
    const {
      data: { user },
    } = await supabase.auth.getUser();
    if (!errorMessage && user) {
      dispatch(addUserData({ user: user.id }));
      const onboarded = await checkIfOnboarded(user.id);
      if (Object.keys(onboarded).length > 0) {
        // set logged in to true
        dispatch(
          addUserData({
            categories: onboarded[0].categories,
            name: onboarded[0].name,
            username: onboarded[0].username,
            savedPosts: onboarded[0].saved_posts
              ? onboarded[0].saved_posts
              : [],
            avatar: onboarded[0].avatar,
            onboarded: true,
          })
        );
        setSignInButtonDisabled(false);
      } else {
        navigation.navigate("Onboarding", { user: user.id });
        setSignInButtonDisabled(false);
      }
    } else {
      setError("error");
      Keyboard.dismiss();
      setSignInButtonDisabled(false);
    }
  };

测试方案优化

要验证onboarded状态与对应屏幕的匹配关系,可从两个维度设计测试:直接测试导航容器的状态渲染逻辑,或验证完整登录流程后的状态切换。

方案1:直接测试导航容器的状态渲染

跳过登录流程,直接控制Redux状态,验证对应屏幕的渲染:

import { render, screen, waitFor } from '@testing-library/react-native';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import RootNavigationContainer from './RootNavigationContainer';

const mockStore = configureStore([]);

describe('RootNavigationContainer', () => {
  it('渲染BottomTabNavigation当onboarded为true时', async () => {
    // 创建Mock Store,设置onboarded状态为true
    const store = mockStore({
      user: {
        onboarded: true,
        // 补充其他必要的state字段
      },
    });

    render(
      <Provider store={store}>
        <RootNavigationContainer />
      </Provider>
    );

    // 验证BottomTabNavigation存在(需给该组件添加testID="bottom-tab-nav")
    await waitFor(() => {
      expect(screen.getByTestId('bottom-tab-nav')).toBeTruthy();
    });

    // 验证登录屏幕不存在
    expect(screen.queryByTestId('email-input')).toBeNull();
  });

  it('渲染LoginEmailScreen当onboarded为false时', async () => {
    const store = mockStore({
      user: {
        onboarded: false,
      },
    });

    render(
      <Provider store={store}>
        <RootNavigationContainer />
      </Provider>
    );

    // 验证登录屏幕存在
    await waitFor(() => {
      expect(screen.getByTestId('email-input')).toBeTruthy();
    });

    // 验证底部导航不存在
    expect(screen.queryByTestId('bottom-tab-nav')).toBeNull();
  });
});

方案2:验证完整登录流程后的状态切换

如果需要测试登录成功后状态更新+导航切换的完整链路,可补充以下测试:

it("登录成功后切换到BottomTabNavigation(已完成onboard用户)", async () => {
    const { getByTestId, queryByTestId } = renderWithProviders(
      <LoginEmailScreen navigation={navigation as any} route={mockRoute} />
    );
    onboarded = true; // 触发getUser返回mockUserId

    // 执行登录操作
    fireEvent.changeText(getByTestId("email-input"), "test@test.de");
    fireEvent.changeText(getByTestId("password-input"), "test");
    fireEvent.press(getByTestId("login-button"));

    // 等待状态更新和导航切换完成
    await waitFor(() => {
      expect(checkIfOnboarded).toHaveBeenCalledWith("mockUserId");
      // 验证登录屏幕已消失
      expect(queryByTestId("email-input")).toBeNull();
      // 验证底部导航已渲染
      expect(getByTestId("bottom-tab-nav")).toBeTruthy();
    });
  });

注意事项

  • 给BottomTabNavigation组件添加testID,方便测试查询元素。
  • 确保renderWithProviders正确包裹Redux Provider和Navigation容器,保证测试环境与真实环境一致。
  • 查询不存在的元素时使用queryByTestId,避免测试抛出错误。

内容的提问来源于stack exchange,提问作者Asaraspo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 03:15:03