'How do I trigger a change event on radio buttons in react-testing-library?

I'm in the process of moving over to react-testing-library, and have no idea how to trigger this event and get the results of the changes.

I've tried using the fireEvent function to trigger the change, and then tried the rerender function, but I can't seem to get it to work.

App.js

import React, { useState } from "react";
import logo from "./logo.svg";
import "./App.css";

const options = {
  DoTheThing: 'DoTheThing',
  DoOtherThing: 'DoOtherThing',
};

function App() {
  const [action, setAction] = useState(options.DoTheThing);

  return (
    <div className="App">
      <header className="App-header">
        <form>
          <fieldset>
            <label>
              <input
                type="radio"
                name="radio1"
                value={options.DoTheThing}
                checked={action === options.DoTheThing}
                onChange={event => setAction(event.target.value)}
              />
              First
            </label>

            <label>
              <input
                type="radio"
                name="radio1"
                value={options.DoOtherThing}
                checked={action === options.DoOtherThing}
                onChange={event => setAction(event.target.value)}
              />
              Second
            </label>
          </fieldset>
        </form>
      </header>
    </div>
  );
}

export default App;

App.test.js

import React from 'react';
import { render, cleanup, fireEvent } from 'react-testing-library';
import App from './App';

afterEach(cleanup);

it('should change the value ', () => {
  const {getByLabelText, rerender } = render(<App/>);
  const second = getByLabelText(/Second/);

  fireEvent.change(second);
  rerender(<App/>);

  expect(document.forms[0].elements.radio1.value).toEqual("DoOtherThing");

});


Solution 1:[1]

if you have a label like with Radio Component of Material-ui, you could use:

const labelRadio: HTMLInputElement = getByLabelText('Label of Radio');
expect(labelRadio.checked).toEqual(false);
fireEvent.click(labelRadio);
expect(androidRadio.checked).toEqual(true);

or you can add https://github.com/testing-library/jest-dom matchers and check it in this way:

expect(getByLabelText('Label of Radio')).not.toBeChecked();
fireEvent.click(labelRadio);
expect(getByLabelText('Label of Radio')).toBeChecked();

Solution 2:[2]

As of May 2020 using React 16.13 and react-testing-library 10.0, the accepted answer does not work (the test itself passes but doesn't actually do anything meaningful).

I can't find any reference to radio buttons in the documentation for react-testing-library or even React itself. However, here's an example (using Typescript) that works properly as far as I can tell.

import React from "react";
class State {
    radioValue: string = "one"
}
export default class RadioTest extends React.Component<{}, State>
{
    state: State = new State();

    radioClick = (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
        this.setState({ radioValue: event.currentTarget.value });
    }

    render() {
        return (<>
            <label>
                One
                <input type="radio" name="radio" onClick={this.radioClick}
                    value="one" onChange={() => {}}
                    checked={this.state.radioValue === "one"} />
            </label>
            <label>
                Two
                <input type="radio" name="radio" onClick={this.radioClick}
                    value="two" onChange={() => {}}
                    checked={this.state.radioValue === "two"} />
            </label>
            <div>current value={this.state.radioValue}</div>
            <button onClick={() => this.setState({radioValue:"one"})}>Click</button>
        </>);
    }
}

test("radiotest", () => {
    const { getByLabelText, queryByText, getByText } = render(<RadioTest />);
    const one = getByLabelText('One') as HTMLInputElement
    const two = getByLabelText('Two') as HTMLInputElement
    expect(one).toBeChecked();
    expect(two).not.toBeChecked();
    expect(queryByText("current value=one")).toBeTruthy();
    fireEvent.click(two);
    expect(one).not.toBeChecked();
    expect(two).toBeChecked();
    expect(queryByText("current value=two")).toBeTruthy();
    fireEvent.click(getByText("Click"))
    expect(one).toBeChecked();
    expect(two).not.toBeChecked();
    expect(queryByText("current value=one")).toBeTruthy();
});

React onChange handlers will work in the browser but not with react-testing-library because they don't fire when you call fireEvent.change()

The dummy onChange handlers are required to avoid a React warning: "If the field should be mutable use defaultChecked". You can't use defaultChecked though, because it stops you setting the state in code (i.e. clicking the button at the bottom doesn't update the radio)

So on the whole, it looks like React wants you to use onChange but react-testing-library only works with onClick, so this is a bit of a fudge.

Solution 3:[3]

Complementing @andy's answer, this should test two radios effectly:

  it('should render successfully', async () => {
    render(
      <YourRadioGroup />
    );

    expect(screen.getByText('option 1')).toBeInTheDocument();
    expect(screen.getByText('option 2')).toBeInTheDocument();
  });

  it('should change checked option', () => {
    render(
      <YourRadioGroup />
    );

    const secondRadio = screen.getByLabelText('option 2');
    fireEvent.click(secondRadio);
    expect(secondRadio).toBeChecked();

    const firstRadio = screen.getByLabelText('option 1');
    fireEvent.click(firstRadio);
    expect(firstRadio).toBeChecked();
    expect(secondRadio).not.toBeChecked();
  });

Solution 4:[4]

This worked for me (working with the radio buttons, not the radio groups):

// some code here to make sure the screen has finished rendering, and I have all radio buttons in the DOM (I am expecting 5 containers):
await waitFor(() => expect(screen.getAllByTestId('some-slow-loading-container')).toHaveLength(5))

// get all "true" labeled radio buttons by test id (or by role + name or whatever):
const allTrueLabelRadioButtons = screen.getAllByTestId('true-label-radio-button');

// loop over the array of HTML elements found:
for (const trueLabelRadioButton of allTrueLabelRadioButtons) {
   // using fireEvent instead of userEvent because of some bugs with the components library I am stuck with. Usually I use userEvent:
   fireEvent.click(trueLabelRadioButton)
}

// check whatever you are expecting to happen after all radio buttons are set to "true".
//...

Solution 5:[5]

Please try this from react-testing-library docs, "render" should work fine. Agree with @Gpx

fireEvent.change(input, { target: { value: 'your_value_goes_here' } })
expect(input.value).toBe('expected_value')

Solution 6:[6]

I've also had this work for me:

test('Does stuff', async () => {
// ... test prep ...

const formEl = screen.getByTestId('form_test_id')

// You can use screen.getByLabelText here instead of DOM queries 
// if you've got a nicely laid out form
const defaultInput = formEl.querySelector('input[value="default_value"]')
const newValueInput = formEl.querySelector('input[value="new_value"]')

// Confirm your baseline
expect(defaultInput.checked).toEqual(true)
expect(newValueInput.checked).toEqual(false)

// Fire the change event
await act(async () => {
fireEvent.change(newValueInput, { target: { checked: true } }) 
// To trigger any onChange listeners
fireEvent.blur(newValueInput)
})

// Confirm expected form state(s)
expect(defaultInput.checked).toEqual(false)
expect(newValueInput.checked).toEqual(true)

})

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Gaspar
Solution 4 MajoRR
Solution 5 rpandey
Solution 6