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

如何用Jest、Next.js 13.4和next-themes测试主题切换后的图标显示?

如何测试ThemeButton组件根据主题显示对应图标?

问题描述

我刚开始学习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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 22:38:10