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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 19:14:56