How to test a className with Jest and React testing library

How to test a className with Jest and React testing library

When testing JavaScript applications, one of the most common checks I see is if an element has a specific class or an element with some class has some text.

Think of an Alert like this:

<div role="alert" className="alert alert-error">
  Something went wrong
</div>

Checks like these with Enzyme were pretty straightforward to accomplish:

const component = mount(
  <Alert type="error" message="Something went wrong" />
);

expect(
  component.find('.alert.alert-error').text()
).toBe('Something went wrong');

But what if you're using React Testing Library?

One of the core philosophies behind React Testing Library is to prevent you from testing implementation details. In other words, you can't easily test things the user won't see. .alert is such an implementation detail because the user can't see it.

There's a nice paragraph explaining why this is the reason in the docs:

React Testing Library aims to test the components how users use them. Users see buttons, headings, forms and other elements by their role, not by their id, class, or element tag name. Therefore, when you use React Testing Library you should avoid accessing the DOM with the document.querySelector API. (You can use it in your tests, but it's not recommended for the reasons stated in this paragraph.)

https://testing-library.com/docs/react-testing-library/migrate-from-enzyme

Considering this philosophy, you would write a test like this in React Testing Library:

render(<Alert type="error" message="Something went wrong" />);

expect(screen.getByText('Something went wrong')).toBeInTheDocument();

But this isn't as accurate as the enzyme version of the test. Here's why:

  1. What if the text due for some reason appears in multiple places in the document, but you're only interested in the alert

  2. What if the error class is applied dynamically based on some constraint, and you want to make sure, in this specific case, it gets applied

So sometimes, you want to ensure that when an error-type alert is displayed, it has the correct error class.

There are 3 different ways to accomplish this:

Using render's container

container contains the result of your rendering, and you can run queries like querySelector or getElementsByClassName on them. To check if the alert appeared with the correct class and message, you could do something like this:

const { container } = render(
  <Alert type="error" message="Something went wrong" />
);
expect(
  container.querySelector('.alert.alert-error')
).toHaveTextContent('Something went wrong');

This approach should be used as a last resort. To quote the docs again:

Users see buttons, headings, forms and other elements by their role, not by their id, class, or element tag name. Therefore, when you use React Testing Library you should avoid accessing the DOM with the document.querySelector API.

Using the selector option

I prefer not to use queries on containers, and thankfully getByText lets us choose text in the DOM by matching both the text itself and the surrounding element using selector:

render(<Alert type="error" message="Something went wrong" />);

expect(
  screen.getByText(
    'Something went wrong',
    { selector: '.alert.alert-error' }
  )
).toBeInTheDocument();

Using role

This is the best solution, but in my experience with React/enzyme codebases role attributes aren't always used correctly, and some apps lack them entirely.

If you're working on an app that uses roles correctly, congrats! You can write the nicest possible React Testing Library test to check if the alert appeared and if it has the correct classes without breaking the library's convention:

render(<Alert type="error" message="Something went wrong" />);

expect(
  screen.getByRole(
    'alert',
    { name: 'Something went wrong' }
  )
).toHaveClass('alert', 'alert-error');

Conclusion

In this article, we explore three different ways to check if an element contains the expected text and if the element itself has the correct classes.

Which type of check you'll apply highly depends on the quality of the current codebase, but as a rule of thumb, try to go with the [get|query|find]By selectors with the combination of the selector options and the toHaveClass function.

Try to avoid testing implementation details as much as possible. Doing so will improve the resiliency of your codebase and will result in a much better development experience.

I hope you learned something new today!

If you have questions, please share them in the comments below.

Thank you for reading this post, and see you in the next one.

Bye! 👋

Did you find this article valuable?

Support Ákos Kőműves by becoming a sponsor. Any amount is appreciated!