170

I am totally new to JavaScript testing and am working in a new codebase. I would like to write a test that is checking for a className on the element. I am working with Jest and React Testing Library. Below I have a test that will render a button based on the variant prop. It also contains a className and I would like to test that.

it('Renders with a className equal to the variant', () => {
    const { container } = render(<Button variant="default" />)
    expect(container.firstChild) // Check for className here
})

I tried to google for a property like Enzyme has with hasClass, but I couldn't find anything. How can I solve this with the current libraries (React Testing Library and Jest)?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Giesburts
  • 6,879
  • 15
  • 48
  • 85
  • 5
    Following up on @AnonymousSB comment, Enzyme is great if you're more concerned with testing implementation, whereas the React Testing Library is for those taking a more user behavior-centric approach to testing. – James B. Nall Jul 14 '20 at 14:19
  • 2
    Following up on both of these - developers considering using Enzyme in a new project should note that, according to Enzyme's docs, it only supports up to React 16. There do not appear to be plans to upgrade to support current versions of React. More info at https://dev.to/wojtekmaj/enzyme-is-dead-now-what-ekl – Andrew Mar 21 '22 at 17:09
  • @AnonymousSB and JamesB.Nall I think they don't include a getByClassName so that people move away from the "className is identifier" logic. I have worked extensively with Enzyme, and that's what I used to use. Rather, use some other means to get the element (how a user would find it in the page) and then check for a specific class in that container based on some conditions (e.g. "active" if a specific prop is set) – cst1992 Sep 01 '22 at 08:44

8 Answers8

216

You can easily do that with react-testing-library.

First, you have to understand that container or the result of getByText etc. are merely DOM nodes. You can interact with them in the same way you would do in a browser.

So, if you want to know what class is applied to container.firstChild you can just do it like this container.firstChild.className.

If you read more about className in MDN you'll see that it returns all the classes applied to your element separated by a space, that is:

<div class="foo">     => className === 'foo'
<div class="foo bar"> => className === 'foo bar'

This might not be the best solution depending on your case. No worries, you can use another browser API, for example classList.

expect(container.firstChild.classList.contains('foo')).toBe(true)

That's it! No need to learn a new API that works only for tests. It's just as in the browser.

If checking for a class is something you do often you can make the tests easier by adding jest-dom to your project.

The test then becomes:

expect(container.firstChild).toHaveClass('foo')

There are a bunch of other handy methods like toHaveStyle that could help you.


As a side note, react-testing-library is a proper JavaScript testing utility. It has many advantages over other libraries. I encourage you to join the spectrum forum if you're new to JavaScript testing.

Gio Polvara
  • 23,416
  • 10
  • 66
  • 62
  • Hey! I see that you are using `toBeTrue` but for some reason I get the `TypeError: expect(...).toBeTrue is not a function`. I run the latest Jest version. The `toHaveClass` is working fine! – Giesburts Nov 20 '18 at 11:22
  • My bad it's `toBe(true)` I fixed it. I use `toHaveClass` though, way easier – Gio Polvara Nov 20 '18 at 14:47
  • @GiorgioPolvara-Gpx I still think it's a hacky way to do it when the library doesn't have support for getByClass method. – Jaspreet Singh Jul 16 '19 at 05:26
  • The library doesn't have a `getByClass` method because it wants to push you to test as a user would. Users don't see classes. But if for some reason the rendered classes are something you want to test this is the way to do it. – Gio Polvara Jul 16 '19 at 08:54
  • @GiorgioPolvara-Gpx how would you test multiple classes like the example `foo bar`? I tried to add `expect(container.firstChild.classList.contains('foo bar')).toBe(true)` but this fails, unless i'm missing something. – medev21 Feb 21 '20 at 23:57
  • 1
    You need to do it in two steps, one for `foo` and the other for `bar` – Gio Polvara Feb 25 '20 at 14:18
  • strangly after an update `expect(container.firstChild).toHaveClass('foo')` no more works in some of my tests, `expect(container.firstChild.classList.contains('foo')).toBe(true)` yes – AlainIb Dec 04 '20 at 11:16
90

The library gives access to normal DOM selectors, so we can also simply do this:

it('Renders with a className equal to the variant', () => {
    const { container } = render(<Button variant="default" />)
    expect(container.getElementsByClassName('default').length).toBe(1);
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jbmilgrom
  • 20,608
  • 5
  • 24
  • 22
  • Don't forget to destructure container, or you'll get `Property 'getElementsByClassName' does not exist on type 'RenderResult...` – SeanMC Aug 02 '23 at 15:49
47

You need to understand the philosophy behind react-testing-library to understand what you can do and what you can't do with it;

The goal behind react-testing-library is for the tests to avoid including implementation details of your components and rather focus on writing tests that give you the confidence for which they are intended.

So querying element by classname is not aligned with the react-testing-library philosophy as it includes implementation details. The classname is actual the implementation detail of an element and is not something the end user will see, and it is subjected to change at anytime in the lifecycle of the element.

So instead of searching an element by what the user cannot see, and something that can change at anytime, just try to search by using something that the user can see, such as text, label or something that will remain constant in the life cycle of the element like data-id.

So to answer your question, it is not advised to test classname and hence you cannot do that with react-testing-library. Try with other test libraries such as Enzyme or react-dom test utils.

Jasperan
  • 2,154
  • 1
  • 16
  • 40
kasongoyo
  • 1,748
  • 1
  • 14
  • 20
  • 160
    I've seen this answer given before here and from the authors of react-testing-library. I've never understood it. "classNames" are, by definition, things that users will see. They are at the very front lines of the user experience. It would be nice to know if a vital class helper has been applied to an element so that the element, for example, becomes visible. – mrbinky3000 Nov 26 '19 at 18:10
  • 4
    Adding to the above, some css libraries like semantic UI make the DOM more readable by looking at its CSS classes. So you have something like
    or a
    etc. Without querying by class its hard to assert when using sematic ui
    – sethu Jan 16 '20 at 20:54
  • 4
    @mrbinky3000 you can test the presence/absence of class in the element and that's very fine and well supported by RTL when used with [jest-dom](https://github.com/testing-library/jest-dom). What's ant-pattern is to locate the element by using className in tests. – kasongoyo Jan 17 '20 at 09:03
  • 2
    @mrbinky3000 Users can't see class names. Although in practice it's not always possible to test something without testing implementing details. – dan-klasson May 13 '20 at 13:14
  • 20
    Messing up the prod cod with data-testid is bigger no for me, than using the class attribute to browse in the rendered code. Not to mention that you don't really have a choice, if you want to check the render of a 3rd party code. Unless you want to mock out everything. Which about people are also making videos not to do. I think it's the case of great philosophy but not practical approach. – bmolnar Dec 24 '20 at 11:27
  • 2
    The authors actually realized the principles could not hold for all use cases (in my case it was testing for the presence of a spinner/loader during a network request) and so they wrote: `"In the spirit of the guiding principles, it is recommended to use this (data-testid) only after the other queries don't work for your use case. Using data-testid attributes do not resemble how your software is used and should be avoided if possible. That said, they are way better than querying based on DOM structure or styling css class names."` I guess it's up to you! – kawerewagaba Jun 20 '21 at 14:03
  • 3
    If your main concern is that your prod code has data-testid, those can always be stripped out during bundling. Ex: babel-plugin-jsx-remove-data-test-id – Rui Marques Sep 24 '21 at 10:44
  • Don't get bogged down with the guidelines, use the queries first and foremost. But if you have some piece of UI that you need to assert on in some other way, like looking up by class name, just do it! Nobody will die – Neil Dec 22 '21 at 10:49
  • Philosophy is one thing, using not finished open source libraries is other thing. U just have your daily duty to check for simple thing like if the prop is true or false, or if buttons have same kind of class etc... so I can't understand libraries like RTL try to look cool, and hey, this is how u will write tests from tommorow... bad thing library like enzyme is half done, and is crap.... – Игор Ташевски Feb 02 '22 at 13:00
  • 1
    Sorry to pile on here, but this answer, and the RTL philosophy, seems to ignore the fact that in many cases, the only way to test what the user sees is to look at the CSS classes. Changes in font, color, size, etc. can not be tested in any other way. That's why most other front end testing tools like Cypress have support for CSS selectors built right in. Querying the DOM by CSS selector should be supported out of the box in any front end testing library, IMHO. – mpelzsherman Aug 17 '22 at 18:25
  • Some react component libraries (especially in development or proprietary corpo stuff) will not even allow for data-testid's to be passed to some components. Feels bad yet typical to be stuck because two parties aren't on the same page. – Daniel Thompson Jul 18 '23 at 16:54
29

You can use testing-library/jest-dom custom matchers.

The @testing-library/jest-dom library provides a set of custom jest matchers that you can use to extend jest. These will make your tests more declarative, clear to read and to maintain.

https://github.com/testing-library/jest-dom#tohaveclass

it('Renders with a className equal to the variant', () => {
    const { container } = render(<Button variant="default" />)

    expect(container.firstChild).toHaveClass('class-you-are-testing') 
})

This can be set up globally in a setupTest.js file

import '@testing-library/jest-dom/extend-expect';
import 'jest-axe/extend-expect';
// etc
glenrothes
  • 1,543
  • 1
  • 14
  • 17
12

You should use toHaveClass from Jest. No need to add more logic.

it('Renders with a className equal to the variant', () => {
    const { container } = render(<Button variant="default" />)
    expect(container.firstChild).toHaveClass(add you className);
//You Can Also You Screen Instead Of Using Container Container Not Recommended To Use As Documentation Said
    expect(screen.getByRole('button')).toHaveClass(add you className)
})
ggorlen
  • 44,755
  • 7
  • 76
  • 106
Yazan Najjar
  • 1,830
  • 16
  • 10
  • Basically the same as [existing](https://stackoverflow.com/a/53391469/6243352) [answers](https://stackoverflow.com/a/62342922/6243352) but with less context. – ggorlen May 20 '22 at 17:21
5

You can use toHaveClass from jest DOM

it('renders textinput with optional classes', () => {
  const { container } = render(<TextArea {...props} className='class1' />)
  expect(container.children[1]).toHaveClass('class1')
})

Don't forgot to destructure response like this {container} Because By default, React Testing Library will create a div and append that div to the document.body and this is where your React component will be rendered. If you provide your own HTMLElement container via this option, it will not be appended to the document.body automatically.

Penny Liu
  • 15,447
  • 5
  • 79
  • 98
Shamaz saeed
  • 387
  • 3
  • 8
0
it('check FAQ link is working or not', () => {
    const mockStore = configureStore({ reducer: Reducers });
    const { container } = render(
      <GraphqlProvider>
        <Provider store={mockStore}>
          <BrowserRouter>
            <FAQ />
          </BrowserRouter>
        </Provider>
      </GraphqlProvider>,
    );
    const faqLink = container.getElementsByClassName('breadcrumb-item active');
    expect(faqLink[0].textContent).toBe('FAQ /');
  });
});
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 16 '23 at 01:12
  • You should add explanation. – Super Kai - Kazuya Ito Jun 16 '23 at 13:42
  • Don't forget to destructure container, or you'll get `Property 'getElementsByClassName' does not exist on type 'RenderResult...` – SeanMC Aug 02 '23 at 15:50
-2
    // Link.react.test.js
import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow';
import App from './../../src/App'
describe('React', () => {
  it('className', () => {
    const renderer = new ShallowRenderer();
    renderer.render(<App />);
    const result = renderer.getRenderOutput();
    expect(result.props.className.split(' ').includes('welcome-framework')).toBe(true);
  });
});
YinPeng.Wei
  • 552
  • 3
  • 9