如何测试使用useFonts及自定义Hook的Expo _layout.tsx组件?
如何测试使用useFonts及自定义Hook的Expo _layout.tsx组件?
嘿,我来帮你梳理下这个Expo Router根布局组件的测试思路和具体实现~ 结合你用的Jest和React Native Testing Library,咱们可以从几个核心场景入手:
首先明确要测试的核心点
- 字体加载阶段的UI状态:当字体还没加载好时,组件应该返回
null,避免渲染错误内容 - 字体加载完成后的完整渲染:确保加载成功后,
ThemeProvider、导航栈和状态栏都正确渲染 - 主题切换逻辑:根据
useColorScheme返回的light/dark值,ThemeProvider要传入对应的主题 - 导航栈结构正确性:确认
Stack里包含指定的两个屏幕,且index屏幕的headerShown选项是false - 状态栏配置:检查
StatusBar的style是否设为auto
具体测试代码实现
首先我们需要mock掉外部依赖(比如useFonts、自定义的useColorScheme以及Expo Router的Stack组件),这样可以隔离测试,只关注RootLayout自身的逻辑:
import React from 'react'; import { render } from '@testing-library/react-native'; import RootLayout from '../app/_layout'; import { useFonts } from 'expo-font'; import { useColorScheme } from '@/hooks/useColorScheme'; // Mock 外部依赖 jest.mock('expo-font', () => ({ useFonts: jest.fn(), })); jest.mock('@/hooks/useColorScheme', () => ({ useColorScheme: jest.fn(), })); // Mock Expo Router的Stack组件,只验证传入的props和子组件 jest.mock('expo-router', () => ({ Stack: { Screen: jest.fn(({ name, options }) => null), __esModule: true, default: jest.fn(({ children }) => <>{children}</>), }, })); describe('RootLayout', () => { // 每个测试前重置所有mock,避免测试间的状态污染 beforeEach(() => { jest.clearAllMocks(); }); it('字体未加载完成时返回null', () => { // Mock useFonts返回未加载状态 (useFonts as jest.Mock).mockReturnValue([false]); const { container } = render(<RootLayout />); expect(container.children.length).toBe(0); }); it('字体加载完成后渲染完整布局结构', () => { (useFonts as jest.Mock).mockReturnValue([true]); (useColorScheme as jest.Mock).mockReturnValue('light'); const { getByType } = render(<RootLayout />); // 验证ThemeProvider是否存在 const ThemeProvider = require('@react-navigation/native').ThemeProvider; expect(getByType(ThemeProvider)).toBeTruthy(); // 验证StatusBar的style属性为auto const StatusBar = require('expo-status-bar').StatusBar; const statusBar = getByType(StatusBar); expect(statusBar.props.style).toBe('auto'); }); it('根据colorScheme自动切换主题', () => { (useFonts as jest.Mock).mockReturnValue([true]); const { DarkTheme, DefaultTheme, ThemeProvider } = require('@react-navigation/native'); // 测试浅色主题场景 (useColorScheme as jest.Mock).mockReturnValue('light'); const { getByType } = render(<RootLayout />); let themeProvider = getByType(ThemeProvider); expect(themeProvider.props.value).toBe(DefaultTheme); // 测试深色主题场景 (useColorScheme as jest.Mock).mockReturnValue('dark'); const { getByType: getByTypeDark } = render(<RootLayout />); themeProvider = getByTypeDark(ThemeProvider); expect(themeProvider.props.value).toBe(DarkTheme); }); it('渲染正确的导航栈屏幕及配置', () => { (useFonts as jest.Mock).mockReturnValue([true]); (useColorScheme as jest.Mock).mockReturnValue('light'); const { Screen } = require('expo-router').Stack; render(<RootLayout />); // 验证index屏幕的配置 expect(Screen).toHaveBeenCalledWith( expect.objectContaining({ name: 'index', options: { headerShown: false }, }), expect.anything() ); // 验证+not-found屏幕的配置 expect(Screen).toHaveBeenCalledWith( expect.objectContaining({ name: '+not-found', }), expect.anything() ); }); });
一些额外小贴士
- 如果你想测试字体文件是否正确引入,可以额外mock字体的require语句,比如:
jest.mock('../assets/fonts/SpaceMono-Regular.ttf', () => 'mock-font-path'); - 对于自定义的
useColorScheme,测试时可以自由控制它的返回值,模拟系统主题切换的各种场景 - 所有mock在
beforeEach里重置,能保证每个测试用例都是独立的,不会互相干扰
内容来源于stack exchange
相关产品推荐
相关产品推荐

