loading...

Struggle with React and unit tests

nans profile image Nans Dumortier ・2 min read

 Context

I'm trying to learn TDD at the moment, and so I decided to exercise on my Gatsby project.

This is the blog I'm working on.

 What do I want to achieve ?

I wanted to add a theme toggler to it, simply by using gatsby-plugin-dark-mode.

So I went ahead and created a theme-toggle.js along with a theme-toggle.test.js file.

 Then, I decided to write my first test. But for that, I had to ...

Setup tests on a Gatsby Project

I followed this tutorial which is well made, along with this one.

Now I can run npm run test -- --watch and everything seems to be working.

Here I am, with jest, @testing-library/react and everything ready to go.

Now, what should I do ?

I've been able to create a simple React component that contains a label with a check box, and successfully tested it with :

describe('ThemeToggle', () => {
  it('renders an input', async () => {
    const themeToggle = render(<ThemeToggle />);
    const toggle = themeToggle.getByLabelText('Dark mode');
    expect(toggle).not.toBeNull(); // this passes ✅
  });
});

But then, I "cheated" for a bit, and looked at the tutorial to see what I had to build to make it work.

Here is how it should look like :

function ThemeToggle() {
  return (
    <ThemeToggler>
      {({ theme, toggleTheme }) => (
        <label htmlFor="themeToggle">
          <input
            id="themeToggle"
            type="checkbox"
            onChange={(e) => toggleTheme(e.target.checked ? 'dark' : 'light')}
            checked={theme === 'dark'}
          />
          {' '}
          Dark mode
        </label>
      )}
    </ThemeToggler>
  );
}

So now my question is : how do I write suitable tests for that component ?

I'm a bit lost 😅

Thank you for your help !

Discussion

pic
Editor guide
Collapse
sargalias profile image
Spyros Argalias

According to the documentation, the end result is that the <body> element gets a class.

If using React testing library and Jest, here's the test I would write:

import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import ThemeToggle from './ThemeToggle';

test('', () => {
  const { getByTestId } = render(<ThemeToggle/>);
  const themeToggleCheckbox = getByTestId('themeToggleCheckbox'); // the checkbox needs data-testid="themeToggleCheckbox" for this

  // check first click changes class to "dark"
  fireEvent.click(themeToggleCheckbox);
  expect(document.body).toHaveClass('dark');
  expect(document.body).not.toHaveClass('light');

  // check second click changes class to "light"
  fireEvent.click(themeToggleCheckbox);
  expect(document.body).toHaveClass('light');
  expect(document.body).not.toHaveClass('dark');
});

Notes:

The fireEvent.click feels very weird to me. In my opinion fireEvent.input is much more appropriate. However it doesn't work. It's just an implementation detail we have to accept for this test.

The reasons we use fireEvent are because: 1. To simulate an end to end test as much as we can (the user would click the checkbox, so we match this as much as we can by firing a click event). 2. Because we actually have to when using React testing library.

Collapse
nans profile image
Nans Dumortier Author

Thank you for your help !
My linter was complaining when I was using document, so I decided to query the container of the toggle.
Then I had to mock window.__setPreferredTheme which is used by the gatsby plugin, and everything worked.
Thanks again for your help :)