javascriptreactjsjestjsreact-testing-libraryvitest

Why is function never called in test


I need help regarding a specific test. I have a Component, that is toggling the sound on and off in my project. The last test, with which I try to test if the e.prevenDefault gets called does not work, and I cant figure out why.

I hope someone has an idea.

import { useAppSelector, useAppDispatch } from '../../../../app/hooks';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faVolumeXmark} from '@fortawesome/free-solid-svg-icons';
import { faVolumeHigh} from '@fortawesome/free-solid-svg-icons';
import { RootState } from '../../../../app/store';
import { toggleMute } from '../../../../features/soundSlice/soundSlice';
import './SoundBtn.css';

export const SoundBtn = () => {
  const isMuted = useAppSelector((state: RootState) => state.sound.isMuted);
  const dispatch = useAppDispatch();

  // Prevent space key from triggering togglePlay
  const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
    if (e.code === ' ' || e.key === 'Space') {
      e.preventDefault(); 
    }
  };

  const togglePlay = () => {
    dispatch(toggleMute());
  };

  return (
    <button 
      className="commandBtn nonHoverBtn" 
      id='soundBtn' 
      onClick={togglePlay}
      onKeyDown={handleKeyDown}
    >
      {!isMuted ? (
        <FontAwesomeIcon 
          icon={faVolumeHigh} 
          className='soundBtnIcon'
          aria-label='click to mute sound'
        />
      ) : (
        <FontAwesomeIcon 
          icon={faVolumeXmark} 
          className='soundBtnIcon'
          aria-label='click to play sound'
        />
      )}
    </button>
  )
};

And this test:

import { fireEvent, screen } from "@testing-library/react";
import { renderWithProviders } from "../../test-utils";
import { SoundBtn } from "../../../components/ui/buttons/soundBtn/SoundBtn";

describe('SoundBtn component', () => {
  it('renders the component and the correct button when muted without crashing', () => {
    const preloadedState = {
      sound: {
        isMuted: true, 
      },
    }; 

    renderWithProviders(<SoundBtn />, { preloadedState });

    expect(screen.getByLabelText('click to play sound')).toBeInTheDocument();
  });

  it('renders the component and the correct button when not muted without crashing', () => {
    const preloadedState = {
      sound: {
        isMuted: false, 
      },
    }; 

    renderWithProviders(<SoundBtn />, { preloadedState });

    expect(screen.getByLabelText('click to mute sound')).toBeInTheDocument();
  });

  it('toggles mute, when button is clicked', () => {
    const preloadedState = {
      sound: {
        isMuted: true, 
      },
    };
        
    const { store } = renderWithProviders(<SoundBtn />, { preloadedState });
    expect(store.getState().sound.isMuted).toBe(true);

    fireEvent.click(screen.getByLabelText('click to play sound'));

    const updatedState = store.getState().sound; 
    expect(updatedState.isMuted).toBe(false); 

    fireEvent.click(screen.getByLabelText('click to mute sound'));

    const newUpdatedState = store.getState().sound; 
    expect(newUpdatedState.isMuted).toBe(true); 
  });

  it('does not toggle sound, when space key is clicked', () => {
    const preloadedState = {
      sound: {
        isMuted: true, 
      },
    };
    const { getByLabelText } = renderWithProviders(<SoundBtn />);
    const button = getByLabelText('click to play sound');

    const preventDefaultSpy = vi.fn();

    const spaceEvent = new KeyboardEvent('keydown', { code: 'Space', key: ' ' });
    fireEvent.keyDown(button , spaceEvent);

    expect(preventDefaultSpy).toHaveBeenCalled();
    const { store } = renderWithProviders(<SoundBtn />, { preloadedState });
    expect(store.getState().sound.isMuted).toBe(true);
  });
});

For the last test I am getting the error:

FAIL  src/__tests__/components/ui/soundBtn.test.tsx > SoundBtn component > does not toggle sound, when space key is clicked
AssertionError: expected "spy" to be called at least once
 ❯ src/__tests__/components/ui/soundBtn.test.tsx:68:35
     66|         fireEvent.keyDown(button , spaceEvent);
     67| 
     68|         expect(preventDefaultSpy).toHaveBeenCalled();

 RERUN  src/__tests__/components/ui/soundBtn.test.tsx x149

Solution

  • I finally found another workaround without testing the function directly. Instead, I just wrote an assertion that tests the state to not have been changed.

    it('does not toggle sound, when space key is clicked', () => {
        const preloadedState = {
            sound: {
                isMuted: true, 
            },
        };
    
        const { getByLabelText, store } = renderWithProviders(<SoundBtn />, { preloadedState });
        const button = getByLabelText('click to play sound');
    
        fireEvent.keyDown(button, { code: 'Space', key: ' ' });
    
        expect(store.getState().sound.isMuted).toBe(true);
    });