Testing click event in React Testing Library
Asked Answered
S

2

36

Here is a simple subcomponent that reveals an answer to a question when the button is clicked:

const Question = ({ question, answer }) => {
    const [showAnswer, setShowAnswer] = useState(false)
    return (
        <>
           <article>
               <header>
                    <h2 data-testid="question">{question}</h2>
                    <button onClick={() => setShowAnswer(!showAnswer)}>
                        {
                            !showAnswer ? <FiPlusCircle /> : <FiMinusCircle />
                        }
                    </button>
               </header>
               {
                   showAnswer && <p data-testid="answer">{answer}</p>
               }
           </article>
        </>
    )
}

export default Question;

I am trying to test that when the button is clicked, the onClick attached is called once and the a <p> element appears on the screen:

const onClick = jest.fn()

test('clicking the button toggles an answer on/off', () => {
    render(<Question />);
    
    const button = screen.getByRole('button')
    fireEvent.click(button)
    
    expect(onClick).toHaveBeenCalledTimes(1);
    
    expect(screen.getByTestId('answer')).toBeInTheDocument()
    
    fireEvent.click(button)

    expect(screen.getByTestId('answer')).not.toBeInTheDocument()

    screen.debug()
    
})

RTL says that onClick is not called at all (in the UI it is, as the result is as expected)

Also, if I want to test that this button really toggles the answer element (message should come on and off) how would I test for that?

If I add another fireEvent.click() to the test (simulating the second click on the button which should trigger the answer element off), and add

expect(screen.getByTestId('answer')).not.toBeInTheDocument()

RTL will just not find that element (which is good, I guess, it means it has been really toggled off the DOM). What assertion would you use for this test to pass for that case?

Seppala answered 4/2, 2021 at 9:50 Comment(1)
what is fireEventChamp
I
47

Couple of issues with your approach.

First, creating an onClick mock like that won't mock your button's onClick callback. The callback is internal to the component and you don't have access to it from the test. What you could do instead is test the result of triggering the onClick event, which in this case means verifying that <FiMinusCircle /> is rendered instead of <FiPlusCircle />.

Second, p is not a valid role - RTL tells you which roles are available in the DOM if it fails to find the one you searched for. The paragraph element doesn't have an inherent accessibility role, so you're better off accessing it by its content with getByText instead.

Here's an updated version of the test:

test('clicking the button toggles an answer on/off', () => {
    render(<Question question="Is RTL great?" answer="Yes, it is." />);
    const button = screen.getByRole('button')
    
    fireEvent.click(button)
    // Here you'd want to test if `<FiMinusCircle />` is rendered.
    expect(/* something from FiMinusCircle */).toBeInTheDocument()
    expect(screen.getByText('Yes, it is.')).toBeInTheDocument()
    
    fireEvent.click(button)
    // Here you'd want to test if `<FiPlusCircle />` is rendered.
    expect(/* something from FiPlusCircle */).toBeInTheDocument();
    expect(screen.queryByText('Yes, it is.')).not.toBeInTheDocument()
})
Illfavored answered 4/2, 2021 at 10:47 Comment(0)
L
3

In my case this worked:

it('Does click event', () => {
    const { container } = render(<Component />);

    fireEvent.click(container.querySelector('.your-btn-classname'));

    // click evt was triggered
});
Lodi answered 24/11, 2021 at 14:39 Comment(2)
querySelector is not recommended. See this articleKeg
@IsaacPak My organization has its own component library. This means common DOM elements are prefaced by the component library name (as in <org-button> instead of <button>). This means I can't target components in the usual ways. querySelector() is often the best option for this situation.Matrilateral

© 2022 - 2024 — McMap. All rights reserved.