如何用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
相关产品推荐
相关产品推荐

