React Testing Library: How to test a component that requires the useState hook from its parent?

Viewed 4334

I have a component that I need to pass the state into like this, so the component can change the state in its parent. I am just using React Testing Library without Enzyme but with Jest.

const [open, setOpen] = useState<boolean>(false);
<Toggle value={open} setValue={setOpen} onText='Open' offtext='Closed' />

And the component itself

import React, { Dispatch, SetStateAction } from 'react';

interface PropsShape {
  value: boolean;
  setValue: Dispatch<SetStateAction<boolean>>;
  onText: string;
  offtext: string;
}


function Toggle({ value, setValue, onText, offtext }: PropsShape) {
  return (
    <div className='Toggle'>
      <div
        className='Toggle--slider'
        onClick={() => setValue(!value)}
        data-testid='click'
      >
        <div className={value ? 'on' : 'off'}></div>
      </div>
      <div className='Toggle--label'>{value ? onText : offtext}</div>
    </div>
  );
}

export { Toggle };

And here's the kind of test I'm trying to do

import { useState } from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
import { Toggle } from '../components/toggle';

  test('The user can see the closed text after the toggle has been clicked', async () => {
    const [open, setOpen] = useState<boolean>(false); // CANT DO THIS
    render(
      <Toggle value={open} setValue={setOpen} onText='Open' offtext='Closed' />
    );
    fireEvent.click(screen.getByTestId('click'));
    expect(await screen.queryByText('Closed')).toBeVisible();
  });

How do I set value and setValue in the test so they can be changed when the toggle is clicked? I have tried adding const [open, setOpen] = useState<boolean>(false); but hooks are not allowed?

1 Answers

React Hooks are pieces of code that can be reused inside React components. The fact that you're using a hook inside a test is not allowed because that test is not a React component. What you can do is create a wrapper component just for the test that does that:

 test('The user can see the closed text after the toggle has been clicked', async () => {
    const Wrapper = () => {
      const [open, setOpen] = useState<boolean>(false); // YOU CAN DO THIS INSIDE THE WRAPPER COMPONENT
      return <Toggle value={open} setValue={setOpen} onText='Open' offtext='Closed' />
    };
    render(<Wrapper />);
    fireEvent.click(screen.getByTestId('click'));
    expect(await screen.queryByText('Closed')).toBeVisible();
  });

This test could add more value if the parent component is tested, because in reality, what you're testing here is that a component executes a callback (in your case setValue) and that shows certain value, but the logic inside changing that value is not being tested per se.

Related