如何在 react-testing-library 中的单选按钮上触发更改事件?

Posted

技术标签:

【中文标题】如何在 react-testing-library 中的单选按钮上触发更改事件?【英文标题】:How do I trigger a change event on radio buttons in react-testing-library? 【发布时间】:2019-07-09 02:50:50 【问题描述】:

我正在迁移到 react-testing-library,但不知道如何触发此事件并获得更改的结果。

我尝试使用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");

);

【问题讨论】:

【参考方案1】:

如果你有一个像 Material-ui 的 Radio Component 这样的标签,你可以使用:

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

【讨论】:

【参考方案2】:

首先,您不必致电rerender。仅当您希望组件接收不同的道具时才使用rerender。见link。

每当您调用 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 = getByLabelText('First')
  fireEvent.change(radio,  target:  value: "second"  );
  expect(radio.value).toBe('second')
);

【讨论】:

我最初发送了一个模拟事件,但后来看到一些 [大概] 较旧的信息表明这是不必要的。我只添加了重新渲染,因为我看不到更改本身。 我记得我为什么要重新渲染。最终,我要测试的是值更改后一组特定输入的外观。我的第一步是能够断言收音机发生了变化。我正在使用waitForElement,但它只是超时并抛出。我应该为此创建一个新问题吗? waitForElement 在发生异步事件时很有用,例如您正在等待fetch 回复。在这种情况下,您不需要等待任何事情,因为更新会立即发生。当waitForElement 失败时,我尝试改用wait。如果即使这样也不起作用,问题通常出在其他地方。通常,我的组件根本不会更新。 这不是单选按钮的工作方式。它们的价值是固定的。只有他们的选中状态会改变。 这段代码所做的只是将第一个单选按钮的值更改为“第二个”。现在您只有 2 个值为“second”的单选按钮,但它们都没有被选中。此外,它不会导致响应 onChange 处理程序被调用,所以它不是真的有用【参考方案3】:

截至 2020 年 5 月,使用 React 16.13 和 react-testing-library 10.0,接受的答案不起作用(测试本身通过但实际上并没有做任何有意义的事情)。

我在 react-testing-library 甚至 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 处理程序将在浏览器中工作,但不适用于 react-testing-library,因为当您调用 fireEvent.change() 时它们不会触发

需要虚拟 onChange 处理程序来避免 React 警告:“如果字段应该是可变的,请使用 defaultChecked”。但是你不能使用 defaultChecked,因为它会阻止你在代码中设置状态(即点击底部的按钮不会更新收音机)

所以总的来说,看起来 React 希望你使用 onChange,但 react-testing-library 只适用于 onClick,所以这有点做作。

【讨论】:

这是我们问题的解决方案。 .change 不会触发行为【参考方案4】:

补充@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();
  );

【讨论】:

【参考方案5】:

请从 react-testing-library 文档中尝试这个,“渲染”应该可以正常工作。同意@Gpx

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

【讨论】:

【参考方案6】:

我也有这项工作:

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)

)

【讨论】:

以上是关于如何在 react-testing-library 中的单选按钮上触发更改事件?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 react-testing-library 在 react-select 组件上触发更改事件?

如何使用 react-testing-library 测试由其他组件组成的组件?

如何使用 react-testing-library 测试包装在 withStyles 中的样式化 Material-UI 组件?

如何在使用 react-testing-library 和 jest 完成的单元测试中启用 react-i18n 翻译文件?

如何使用 React-Testing-Library 测试 mapDispatchToProps?

如何使用 react-testing-library 在容器内的元素上触发 Event.scroll?