Check that button is disabled in react-testing-library
Asked Answered
S

10

128

I have a React component that generates a button whose content contains a <span> element like this one:

function Click(props) {
    return (
        <button disable={props.disable}>
            <span>Click me</span>
        </button>
    );
}

I want to test the logic of this component with the use of react-testing-library and mocha + chai.

The problem at which I stuck at the moment is that the getByText("Click me") selector returns the <span> DOM node, but for the tests, I need to check the disable attribute of the <button> node. What is the best practice for handling such test cases? I see a couple of solutions, but all of them sound a little bit off:

  1. Use data-test-id for <button> element
  2. Select one of the ancestors of the <Click /> component and then select the button within(...) this scope
  3. Click on the selected element with fireEvent and check that nothing has happened

Can you suggest a better approach?

Sankaran answered 14/6, 2019 at 7:56 Comment(2)
Test that the disabled attribute of the button element is true?Audry
Not OP's case, but for future visitors that came here looking to test aria-disabled, .toBeDisabled() won't work. The workaround is .toHaveAttribute('aria-disabled', 'true'). See jest-dom #144.Rhaetic
G
213

Assert if button is disabled

You can use the toHaveAttribute and closest to test it.

import { render } from '@testing-library/react';

const { getByText } = render(Click);
expect(getByText(/Click me/i).closest('button')).toHaveAttribute('disabled');

or toBeDisabled

expect(getByText(/Click me/i).closest('button')).toBeDisabled();

Assert if button is enabled

To check if the button is enabled, use not as follows

expect(getByText(/Click me/i).closest('button')).not.toBeDisabled();
Grampus answered 14/6, 2019 at 8:0 Comment(9)
Just a note: there must be a relatevily fresh version of the jsdom library to be able to use closest(...) function in your example (v 11.12.0 or higher)Sankaran
Note that toHaveAttribute requires that github.com/testing-library/jest-dom be installed.Expeditious
For everyone using a different framework than jest, e.g. Jasmine, something like this should do the job: expect(getByText(/Click me/i).closest('button').hasAttribute('disabled')).toBeTrue();Carlsbad
While this answer works, personally I feel like its not aligned to the ethos of react-testing-library. I've provided an alternative answer below that, to me, is more in-tune with rtl (not saying better or worse, just an alternative)Den
TS2339: Property 'toBeDisabled' does not exist on type 'JestMatchers '.Elenore
@DimitriKopriwa have you added @testing-library/jest-dom?Grampus
What I find funny is that even after the assertion of being disabled passes I can click the button and it triggers the callback... why?Phyllisphylloclade
if you want to check for not enabled elements then do this -------------------------- expect(getByText(/Click me/i).getAttribute("disabled")).toBeDefined()Doro
I felt similarly to others about this approach being focused on implementation details, but it's noteworthy that in testing-library's own jest-dom implementation, .toBeDisabled() is just checking the attribute, and they recommend using this matcher for checking disabled state in their own guide.Monsour
G
39

You can use toBeDisabled() from @testing-library/jest-dom, it is a custom jest matcher to test the state of the DOM:

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

Example:

<button>Submit</button>
expect(getByText(/submit/i)).toBeDisabled()
Gendarmerie answered 17/7, 2019 at 19:54 Comment(0)
S
20

For someone who is looking for the test in which the button is not disabled.

import { render } from '@testing-library/react';

const { getByText } = render(Click);
expect(getByText(/Click me/i).getAttribute("disabled")).toBe(null)
Stretto answered 18/6, 2020 at 6:46 Comment(1)
instead of .toBe(null) use .toBeNull().Limey
D
15

I would politely argue you are testing an implementation detail, which react-testing-library discourages.

The more your tests resemble the way your software is used, the more confidence they can give you.

If a button is disabled, a user doesn't see a disabled prop, instead they see nothing happen. If a button is enabled, a user doesn't see the omission of a disabled prop, instead they see something happen.

I believe you should be testing for this instead:

const Button = (props) => (
  <button 
    type="submit" 
    onClick={props.onClick} 
    disabled={props.disabled}
  >
    Click me
  </button>
);

describe('Button', () => {
  it('will call onClick when enabled', () => {
    const onClick = jest.fn();
    render(<Button onClick={onClick} disabled={false} />);
    userEvent.click(getByRole('button', /click me/i));
    expect(onClick).toHaveBeenCalledTimes(1);
  });

  it('will not call onClick when disabled', () => {
    const onClick = jest.fn();
    render(<Button onClick={onClick} disabled={true} />);
    userEvent.click(getByRole('button', /click me/i));
    expect(onClick).not.toHaveBeenCalled();
  });
})
Den answered 8/8, 2021 at 10:20 Comment(4)
Good point! Altough I think that in the case of disabled button you can sacrifice the purity of concept and test "implementation details", especially when you test disabled attribute which is the part of the HTML specification with fixed behaviour. Can you provide some example that will shows cons of testing disabled prop and pros of mocking click callback, just for my interest?Sankaran
As @NikitaSivukhin stated, you are testing the HTML fixed behavior when you check if clicking a disabled button will trigger OnClick. While the user cannot see if a prop is disabled, they will see the visual effect of the disabled prop.Shope
@LoganCundiff Of course. Basically "never say never" - there will always be exceptions to the "rules". I personally wouldn't check for the disabled attribute but there may be cases where it makes more sense to sacrifice the purity of the concept as Nikita said. Ultimately I don't know your codebase and you should do what fits within your team and projectsDen
@Den Yea I think it is good you brought up this point. It's worth asking these questions when considering how to implement a test instead of taking a one fit all approach. Good point.Shope
B
6

toHaveAttribute is good option in using attribute.

<button data-testid="ok-button" type="submit" disabled>ok</button>

const button = getByTestId('ok-button')
//const button = getByRole('button');

expect(button).toHaveAttribute('disabled')
expect(button).toHaveAttribute('type', 'submit')
expect(button).not.toHaveAttribute('type', 'button')

expect(button).toHaveAttribute('type', expect.stringContaining('sub'))
expect(button).toHaveAttribute('type', expect.not.stringContaining('but'))

Hope this will be helpful.

Blumenthal answered 6/7, 2021 at 0:47 Comment(0)
E
3

You can test the disable prop of the button just by using @testing-library/react as follows.

example:

   import { render } from '@testing-library/react';

   const {getByText} = render(<Click/>)

   expect(getByText('Click me').closest('button').disabled).toBeTruthy()
Eba answered 9/4, 2020 at 2:33 Comment(1)
I like this answer because it doesn't rely on another library. Thanks.Merriman
E
1

Another way to fix this would be to grab by the role and check the innerHTML like,

const { getByRole } = render(<Click />)
const button = getByRole('button')

// will make sure the 'Click me' text is in there somewhere
expect(button.innerHTML).toMatch(/Click me/))

This isn't the best solution for your specific case, but it's one to keep in your back pocket if you have to deal with a button component that's not an actual button, e.g.,

<div role="button"><span>Click Me</span></div>

Erfert answered 29/5, 2020 at 19:2 Comment(0)
E
1

My solution, It seems to me that this case covers well what is necessary. Check that the button is disabled, so toHaveBeenCalledTimes must receive 0

 test('Will not call onClick when disabled', () => {
        const mockHandler = jest.fn()

        render(<Button title="Disabled account" disabled={true} onClick={mockHandler} />)
        const button = screen.getByText("Disabled account")
        fireEvent.click(button)

        expect(mockHandler).toHaveBeenCalledTimes(0)
        expect(button).toHaveProperty('disabled', true)
    })
Elaina answered 18/5, 2022 at 19:14 Comment(0)
C
0

Current answer:

expect(await screen.findByRole('button', { name: 'Click me' })).toBeEnabled();
Caponize answered 15/5, 2023 at 14:13 Comment(0)
F
0

You can use the toBeDisabled()

expect(getByText(/Click me/i).closest('button')).toBeDisabled();

Assert if button is enabled

To check if the button is enabled, use not as follows

expect(getByText(/Click me/i).closest('button')).not.toBeDisabled();
Fiftyfifty answered 30/10, 2023 at 18:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.