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

如何测试使用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()
    );
  });
});

一些额外小贴士

  1. 如果你想测试字体文件是否正确引入,可以额外mock字体的require语句,比如:
    jest.mock('../assets/fonts/SpaceMono-Regular.ttf', () => 'mock-font-path');
    
  2. 对于自定义的useColorScheme,测试时可以自由控制它的返回值,模拟系统主题切换的各种场景
  3. 所有mock在beforeEach里重置,能保证每个测试用例都是独立的,不会互相干扰

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 14:28:07