Search code examples
javascriptreactjsunit-testingjestjs

Why is my jesk.mock not work, the theme is undefined


My logo component, based on my next-theme's theme I change the logo and src url for the svg:

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;

The test

Before each test I clearAllMocks, and inside of each test I use jest.mock to change the theme.

The theme is undefined.

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 width and height attributes
      // expect(logoElement).toHaveAttribute('width', '200');
      // expect(logoElement).toHaveAttribute('height', '40');

      // 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 width and height attributes
      // expect(logoElement).toHaveAttribute('width', '144');
      // expect(logoElement).toHaveAttribute('height', '30');

      // Ensure that the image element has the correct title attribute
      expect(logoElement).toHaveAttribute('title', 'bitcoin');
    });
  });
});

The Error:

  ● 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>
    ```



Solution

  • I was able to fix the test, this is how I set the useTheme mock values:

    require('next-themes').useTheme.mockReturnValueOnce({ theme: 'bounty' });
    

    Full updated test:

    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 () => {
        require('next-themes').useTheme.mockReturnValueOnce({ theme: 'bounty' });
    
        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 width and height attributes
          expect(logoElement).toHaveAttribute('width', '200');
          expect(logoElement).toHaveAttribute('height', '40');
    
          // 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 () => {
        require('next-themes').useTheme.mockReturnValueOnce({ theme: 'bitcoin' });
    
        render(<MainLogo />);
    
        await waitFor(() => {
          const logoElement = screen.getByAltText('Logo');
          expect(logoElement).toBeInTheDocument();
          expect(logoElement).toHaveAttribute('width', '144');
          expect(logoElement).toHaveAttribute('height', '30');
          expect(logoElement).toHaveAttribute('title', 'bitcoin');
        });
      });
    });