如何用Jest、Next.js 13.4和next-themes测试主题切换后的图标显示?
问题描述
我刚开始学习Jest,还没完全掌握其整体逻辑,现在需要为我的ThemeButton组件添加测试:该组件通过next-themes的useTheme钩子实现明暗主题切换,dark主题显示太阳图标,light主题显示月亮图标。目前已经能验证按钮渲染,想实现根据当前选中的主题,测试组件显示对应图标的功能。
组件代码
'use client'; /** * ThemeButton Component: * * This component represents a button that allows users to toggle between light and dark themes in the application. * It utilizes the `useTheme` hook from `next-themes` to access the current resolved theme and set the theme to either * 'light' or 'dark' based on the user's preference. The button displays a sun icon for the light theme and a moon icon * for the dark theme. When clicked, it will switch the theme accordingly. The component also handles the initial mount * state to prevent flickering during theme change by setting a `mounted` state to true after the initial rendering. */ import { useTheme } from 'next-themes'; import { useEffect, useState } from 'react'; import { SunIcon, MoonIcon } from '@heroicons/react/24/solid'; const ThemeButton = () => { const [mounted, setMounted] = useState(false); const { resolvedTheme, setTheme } = useTheme(); useEffect(() => setMounted(true), []); if (!mounted) return null; return ( <button aria-label='Toggle Dark Mode' type='button' className='rounded-full p-2 transition-colors hover:bg-gray-900/50 hover:text-gray-50 dark:hover:bg-gray-700 ' onClick={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')}> {resolvedTheme === 'dark' ? ( <SunIcon className='h-7 w-7 text-primary-orange ' /> ) : ( <MoonIcon className='h-7 w-7 text-gray-900 hover:text-gray-50' /> )} </button> ); }; export default ThemeButton;
现有测试代码
import { render, screen } from '@testing-library/react'; import React from 'react'; import '@testing-library/jest-dom/extend-expect'; import ThemeButton from '@/Components/ThemeButton'; test('should render the theme icon', () => { render(<ThemeButton />); const themeIcon = screen.getByLabelText('Toggle Dark Mode'); expect(themeIcon).toBeInTheDocument(); });
解决方案
要实现主题对应图标的测试,核心是模拟useTheme钩子的返回值,并处理组件的mounted状态,确保组件完全渲染后再断言图标。
步骤1:模拟next-themes的useTheme钩子
Jest允许我们模拟第三方模块的导出,这样可以控制useTheme返回的resolvedTheme值,从而测试不同主题下的组件表现。
步骤2:编写异步测试用例
由于组件初始mounted为false会返回null,需要等待useEffect执行后mounted变为true,组件才会渲染出按钮和图标。我们可以用Testing Library的异步查询方法(如findBy*)自动等待组件更新。
完整测试代码
import { render, screen } from '@testing-library/react'; import React from 'react'; import '@testing-library/jest-dom/extend-expect'; import ThemeButton from '@/Components/ThemeButton'; import { useTheme } from 'next-themes'; // 模拟next-themes模块的useTheme钩子 jest.mock('next-themes'); test('显示太阳图标当主题为dark时', async () => { // 设置mock的useTheme返回dark主题 (useTheme as jest.Mock).mockReturnValue({ resolvedTheme: 'dark', setTheme: jest.fn(), // 模拟setTheme方法,不需要实际执行 }); render(<ThemeButton />); // 等待按钮渲染完成(mounted变为true) await screen.findByLabelText('Toggle Dark Mode'); // 断言太阳图标存在,月亮图标不存在 // 注:如果Heroicons图标没有默认aria-label,可通过className或修改组件添加aria-label expect(screen.getByRole('img', { name: /sun/i })).toBeInTheDocument(); expect(screen.queryByRole('img', { name: /moon/i })).not.toBeInTheDocument(); }); test('显示月亮图标当主题为light时', async () => { // 设置mock的useTheme返回light主题 (useTheme as jest.Mock).mockReturnValue({ resolvedTheme: 'light', setTheme: jest.fn(), }); render(<ThemeButton />); await screen.findByLabelText('Toggle Dark Mode'); // 断言月亮图标存在,太阳图标不存在 expect(screen.getByRole('img', { name: /moon/i })).toBeInTheDocument(); expect(screen.queryByRole('img', { name: /sun/i })).not.toBeInTheDocument(); });
补充说明
- 如果Heroicons的图标没有默认的可访问名称(aria-label),建议给组件里的图标添加
aria-label属性,比如:
这样测试时可以更精准地通过<SunIcon aria-label="太阳图标" className='h-7 w-7 text-primary-orange ' /> <MoonIcon aria-label="月亮图标" className='h-7 w-7 text-gray-900 hover:text-gray-50' />name查询图标。 queryBy*方法用于查询可能不存在的元素,而getBy*用于查询必须存在的元素,两者搭配使用可以准确断言图标是否显示。
内容的提问来源于stack exchange,提问作者antonkornilov-ua
相关产品推荐
相关产品推荐

