138

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?

2
  • Test that the disabled attribute of the button element is true? Commented Jun 14, 2019 at 7:58
  • 4
    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. Commented Dec 15, 2022 at 19:29

10 Answers 10

226

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();
Sign up to request clarification or add additional context in comments.

9 Comments

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)
Note that toHaveAttribute requires that github.com/testing-library/jest-dom be installed.
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();
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)
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.
|
40

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()

Comments

21

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)

1 Comment

instead of .toBe(null) use .toBeNull().
16

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();
  });
})

6 Comments

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?
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.
@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 projects
@Chris 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.
Testing the disabled prop is not implementation detail and should be encouraged. If you only test for onClick, the user may see an enabled button that doesn't call the onClick function, causing frustration. Jest wouldn't have implemented the shorthand method toBeDisabled if it was discouraged
|
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.

Comments

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()

1 Comment

I like this answer because it doesn't rely on another library. Thanks.
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>

Comments

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)
    })

Comments

0

Current answer:

expect(await screen.findByRole('button', { name: 'Click me' })).toBeEnabled();

Comments

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();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.