Как вызвать событие изменения на переключателях в реагирующей тестирующей библиотеке?

Я нахожусь в процессе перехода к библиотеке реагирования-тестирования и не знаю, как вызвать это событие и получить результаты изменений.

Я пытался использовать fireEvent функция, чтобы вызвать изменение, а затем попытался rerender функции, но я не могу заставить его работать.

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

});

8 ответов

Решение

Во-первых, вам не нужно звонить rerender, Ты используешь rerender только когда вы хотите, чтобы компонент получил разные реквизиты. Смотрите ссылку.

Всякий раз, когда вы звоните fireEvent компонент будет отображаться так же, как в обычном приложении.

Это правильно уволить change событие, но вы должны передать второй параметр с данными события.

Этот пример работает:

import React from "react";
import { render, fireEvent } from "react-testing-library";

test("radio", () => {
  const { getByLabelText } = render(
    <form>
      <label>
         First <input type="radio" name="radio1" value="first" />
      </label>
      <label>
        Second <input type="radio" name="radio1" value="second" />
      </label>
    </form>
  );

  const radio = getByLabelTest('First')
  fireEvent.change(radio, { target: { value: "second" } });
  expect(radio.value).toBe('second')
});

Если у вас есть метка, такая как Radio Component of Material-ui, вы можете использовать:

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

или вы можете добавить сопоставители https://github.com/testing-library/jest-dom и проверить это следующим образом:

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

По состоянию на май 2020 года с использованием React 16.13 и react-testing-library 10.0 принятый ответ не работает (сам тест проходит, но на самом деле ничего значимого не делает).

Я не могу найти никаких ссылок на переключатели в документации для библиотеки react-testing или даже React. Однако вот пример (с использованием Typescript), который, насколько я могу судить, работает правильно.

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 будут работать в браузере, но не с библиотекой response-testing-library, потому что они не срабатывают при вызове fireEvent.change()

Фиктивные обработчики onChange необходимы, чтобы избежать предупреждения React: "Если поле должно быть изменяемым, используйтеdefaultChecked". Вы не можете использовать defaultChecked, потому что он не позволяет вам устанавливать состояние в коде (т.е. нажатие кнопки внизу не обновляет радио)

В общем, похоже, что React хочет, чтобы вы использовали onChange но библиотека реагирования-тестирования работает только с onClick, так что это немного выдумка.

Дополняя ответ @andy, это должно эффективно протестировать два радио:

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

Пожалуйста, попробуйте это из документации по response-testing-library, "render" должен работать нормально. Согласен с @Gpx

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

Можно использовать указанное ниже решение для RadioGroup. Это будет работать для RTL.

      render(component);
const fieldset = screen.getByLabelText(/other applicable cards/i);
expect(fieldset.checked).toEqual(false);
fireEvent.click(fieldset, { target: { value: '2' } });
expect(fieldset.value).toEqual('2');

Можно использовать для управления несколькими событиями щелчка по радио.

        render(component);
  const fieldset = screen.getByLabelText(/other applicable cards/i);
  expect(fieldset.checked).toEqual(false);
  fireEvent.click(fieldset, { target: { value: '2' } });
  expect(fieldset.value).toEqual('2');
  const fieldset1 = screen.getByLabelText(/original amex platinum/i);
  expect(fieldset1.checked).toEqual(false);
  fireEvent.click(fieldset1, { target: { value: '1' } });
  expect(fieldset1.value).toEqual('1');

Это сработало для меня (работа с переключателями, а не с группами переключателей):

      // 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".
//...

У меня тоже была эта работа:

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)

})
Другие вопросы по тегам