Jest测试next-themes失效:MainLogo组件theme值为undefined问题
问题:Next-themes主题切换组件Jest测试失败,theme始终为undefined
我开发了一个基于next-themes的MainLogo组件,会根据theme值切换SVG Logo的路径、尺寸等属性,组件代码如下:
import { useEffect, useState } from 'react'; import Image from 'next/image'; import { useTheme } from 'next-themes'; function MainLogo() { const [themeClass, setThemeClass] = useState(''); const [weight, setWeight] = useState(200); // Default weight const [height, setHeight] = useState(40); // Default height const { theme } = useTheme() || {}; // Prevents destructuring from undefined useEffect(() => { let newThemeClass = ''; if (theme) { switch (theme) { case 'acme': setWeight(100); setHeight(30); newThemeClass = 'acme'; break; case 'bitcoin': setWeight(144); setHeight(30); newThemeClass = 'bitcoin'; break; case 'bounty': default: setWeight(200); setHeight(40); newThemeClass = 'bounty'; break; } setThemeClass(newThemeClass); } }, [theme]); // Run the effect whenever the theme changes return ( <div> <Image src={`/internal/logo_${themeClass}.svg`} alt="Logo" width={weight} height={height} title={themeClass} /> </div> ); } export default MainLogo;
在Jest测试中,我尝试在每个测试用例里用jest.mock模拟useTheme返回指定theme,且在beforeEach中执行jest.clearAllMocks,但测试时theme始终为undefined,导致logo渲染不符合预期。测试代码及错误信息如下:
测试代码
import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import MainLogo from './logo'; jest.mock('next-themes'); describe('MainLogo Component', () => { beforeEach(() => { jest.clearAllMocks(); }); it('renders the logo properly with the "bounty" theme', async () => { jest.mock('next-themes', () => ({ useTheme: () => ({ theme: 'bounty', // Set the desired theme value for testing with bounty theme }), })); render(<MainLogo />); // Wait for the state to be updated await waitFor(() => { // Ensure that the image element with the correct alt text is rendered const logoElement = screen.getByAltText('Logo'); expect(logoElement).toBeInTheDocument(); // Ensure that the image element has the correct title attribute expect(logoElement).toHaveAttribute('title', 'bounty'); }); }); it('renders the logo properly with the "bitcoin" theme', async () => { jest.mock('next-themes', () => ({ useTheme: () => ({ theme: 'bitcoin', // Set the desired theme value for testing with bitcoin theme }), })); render(<MainLogo />); // Wait for the state to be updated await waitFor(() => { // Ensure that the image element with the correct alt text is rendered const logoElement = screen.getByAltText('Logo'); expect(logoElement).toBeInTheDocument(); // Ensure that the image element has the correct title attribute expect(logoElement).toHaveAttribute('title', 'bitcoin'); }); }); });
错误信息
● MainLogo Component › renders the logo properly with the "bitcoin" theme expect(element).toHaveAttribute("title", "bitcoin") // element.getAttribute("title") === "bitcoin" Expected the element to have attribute: title="bitcoin" Received: title="" Ignored nodes: comments, script, style <html> <head /> <body> <div> <div> <img alt="Logo" data-nimg="1" decoding="async" height="40" loading="lazy" src="/internal/logo_.svg" style="color: transparent;" title="" width="200" /> </div> </div> </body> </html>
问题原因
Jest的jest.mock是静态处理的,会在测试文件加载阶段就执行并锁定模块模拟,测试用例内部的jest.mock不会生效。你在每个测试里重新调用jest.mock的方式完全无效,导致useTheme始终返回默认的mock值(undefined),组件无法获取正确的theme。
解决方案
保留文件顶部的jest.mock('next-themes'),在每个测试用例中通过mockReturnValue动态修改useTheme的返回值:
import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; // 导入useTheme以便修改mock返回值 import { useTheme } from 'next-themes'; import MainLogo from './logo'; // 只在文件顶部声明一次mock jest.mock('next-themes'); describe('MainLogo Component', () => { beforeEach(() => { jest.clearAllMocks(); }); it('renders the logo properly with the "bounty" theme', async () => { // 动态设置useTheme的返回值 useTheme.mockReturnValue({ theme: 'bounty' }); render(<MainLogo />); await waitFor(() => { const logoElement = screen.getByAltText('Logo'); expect(logoElement).toBeInTheDocument(); expect(logoElement).toHaveAttribute('title', 'bounty'); expect(logoElement).toHaveAttribute('width', '200'); expect(logoElement).toHaveAttribute('height', '40'); }); }); it('renders the logo properly with the "bitcoin" theme', async () => { useTheme.mockReturnValue({ theme: 'bitcoin' }); render(<MainLogo />); await waitFor(() => { const logoElement = screen.getByAltText('Logo'); expect(logoElement).toBeInTheDocument(); expect(logoElement).toHaveAttribute('title', 'bitcoin'); expect(logoElement).toHaveAttribute('width', '144'); expect(logoElement).toHaveAttribute('height', '30'); }); }); });
额外优化建议
组件中const { theme } = useTheme() || {};可以改成更直观的可选链写法,避免潜在的解构问题:
const themeContext = useTheme(); const theme = themeContext?.theme;
内容的提问来源于stack exchange,提问作者Leon Gaban
相关产品推荐
相关产品推荐

