在 Jest 中测试 onChange 函数

Posted

技术标签:

【中文标题】在 Jest 中测试 onChange 函数【英文标题】:Testing onChange function in Jest 【发布时间】:2018-06-19 05:09:13 【问题描述】:

我对 Jest 和一般测试比较陌生。我有一个带有输入元素的组件:

import * as React from "react";

export interface inputProps
    placeholder: string;
    className: string;
    value: string;
    onSearch: (depID: string) => void;


onSearch(event: any)
    event.preventDefault();
    //the actual onclick event is in another Component
    this.props.onSearch(event.target.value.trim());


export class InputBox extends React.Component<inputProps, searchState> 
  render() 
        return (
            <input
                onChange=this.onSearch //need to test this
                className=this.props.className 
                type="text"
                value=this.props.value
                placeholder=this.props.placeholder />
        );
    

我想要一个测试来检查输入元素的onChange 是一个以输入元素的value 属性作为参数的函数。这是我到目前为止所取得的成就:

//test to see the input element's onchange 
//returns a function that takes its value as a param
it("onChange param is the same value as the input value", () => 
    const mockFn = jest.fn();
    const input = enzyme.shallow(<InputBox 
                                    value="TestVal"
                                    placeholder="" 
                                    className="" 
                                    onSearch=mockFn/>);


       input.find('input').simulate('change',   preventDefault()  );
       expect(mockFn.mock.calls).toBe("TestVal");
    );

我要离开这里的第一个解决方案Simulate a button click in Jest 还有:https://facebook.github.io/jest/docs/en/mock-functions.html

编辑:运行上述代码会引发以下错误:

 TypeError: Cannot read property 'value' of undefined

【问题讨论】:

【参考方案1】:

我为此苦苦挣扎了好几个小时。另外,因为我在一页上有多个选择字段。我发现 Textfield 解决方案的工作方式与文档上给出的 Select.test 不同。

在代码中我用 id 定义了 SelectProps。 (您也可以使用 data-testid)

我只能通过单击此字段来触发下拉菜单。

<TextField
  select
  variant = "outlined"
  value =  input.value || Number(0) 
  onChange =  value => input.onChange(value) 
  error =  Boolean(meta.touched && meta.error) 
  open =  open 
  SelectProps = 
    
      id: `$input.name-select`,
      MenuProps: 
        anchorOrigin: 
          vertical: "bottom",
          horizontal: "left"
        ,
        transformOrigin: 
          vertical: "top",
          horizontal: "left"
        ,
        getContentAnchorEl: null
      
    
   
   ...props >

  //yourOptions Goes here

 </TextField>

在我的测试中。

const pickUpAddress = document.getElementById("address-select");

UserEvent.click(pickUpAddress);
UserEvent.click(screen.getByTestId("address-select-option-0"));

之后的工作就像一个魅力。希望这会有所帮助。

【讨论】:

【参考方案2】:

这个怎么样?我使用酶模拟更改事件并执行快照测试。 组件

import React,  FunctionComponent, useState  from 'react';

const Index: FunctionComponent = () => 

  const [val, setVal] = useState('');

  const onInputChange = e => 
    e.preventDefault();
    setVal(e.target.value);
  ;

  return (
    <input type='text' onChange=onInputChange value=val />
  );
;

export default Index;

单元测试

describe('Index with enzyme', () => 
  it('Should set value to state when input is changed', () => 
    const container = shallow(<Index />);
    const input = container.find('input');
    input.simulate('change',  preventDefault: jest.fn, target:  value: "foo"  );
    expect(container).toMatchSnapshot();
  );
);

快照

exports[`Index with enzyme Should set value to state when input is changed 1`] = `
  <input
    onChange=[Function]
    type="text"
    value="foo"
  />
`;

【讨论】:

【参考方案3】:

我认为你的代码 sn-p 的语法应该是:

import React from 'react';

export default class InputBox extends React.Component 
  onSearch(event) 
    event.preventDefault();
    this.props.onSearch(event.target.value.trim());
  
  render ()  return (<input onChange=this.onSearch.bind(this) />); 

测试失败是因为,正如您在事件对象上定义 preventDefault 函数一样,您还必须定义在 onSearch 函数上使用的其他属性。

it('should call onChange prop', () => 
  const onSearchMock = jest.fn();
  const event = 
    preventDefault() ,
    target:  value: 'the-value' 
  ;
  const component = enzyme.shallow(<InputBox onSearch=onSearchMock />);
  component.find('input').simulate('change', event);
  expect(onSearchMock).toBeCalledWith('the-value');
);

之前的测试代码需要定义事件形状,因为您使用的是浅层渲染。如果您想测试您的onSearch 函数是否使用了实际输入值,您需要尝试使用enzyme.mount 进行完整渲染:

it('should call onChange prop with input value', () => 
  const onSearchMock = jest.fn();
  const component = enzyme.mount(<InputBox onSearch=onSearchMock value="custom value" />);
  component.find('input').simulate('change');
  expect(onSearchMock).toBeCalledWith('custom value');
);

【讨论】:

不客气! @ZeroDarkThirty 也许你可以通过这个react-seed 项目学到更多东西。 谢谢!顺便说一句,在这种情况下使用mount 而不是shallow 有什么意义? 在这种特殊情况下,主要区别在于挂载的组件会为您创建事件,允许在调用 prop 函数回调 (onSearch) 时测试使用的实际输入值。通常,shallow 渲染用于真正的单元测试,因为没有子组件被渲染。而mountrender 用于完整的 DOM 渲染,当您想要测试完整的组件生命周期时,这是理想的选择......检查完整渲染 API here【参考方案4】:

对于那些使用 TypeScript(并借鉴上面的答案)进行测试的人,您需要执行类型强制 (as React.ChangeEvent&lt;HTMLInputElement&gt;) 以确保 linter 可以将签名视为兼容:

反应文件

export class InputBox extends React.Component<inputProps, searchState> 
  onSearch(event: React.ChangeEvent<HTMLInputElement>)
    event.preventDefault();
    //the actual onclick event is in another Component
    this.props.onSearch(event.target.value.trim());
  

  render() 
    return (
      <input
        onChange=this.onSearch //need to test this
        className=this.props.className 
        type="text"
        value=this.props.value
        placeholder=this.props.placeholder />
      );
  

测试文件

it('should call onChange prop', () => 
  const onSearchMock = jest.fn();
  const event = 
    target:  value: 'the-value' 
   as React.ChangeEvent<HTMLInputElement>;
  const component = enzyme.shallow(<InputBox onSearch=onSearchMock />);
  component.find('input').simulate('change', event);
  expect(onSearchMock).toBeCalledWith('the-value');
);

或者

it('should call onChange prop', () => 
  const onSearchMock = jest.fn();
  const event = 
    target:  value: 'the-value' 
   as React.ChangeEvent<HTMLInputElement>;
  const component = enzyme.mount<InputBox>(<InputBox onSearch=onSearchMock />);
  const instance = component.instance();
  instance.onSearch(event);
  expect(onSearchMock).toBeCalledWith('the-value');
);

【讨论】:

【参考方案5】:

我想出了解决办法。

因此,我们必须在simulate 的第二个参数中传递它,而不是传递InputBox 中的值,如下所示。然后我们只需检查第一次调用mockFn 的第一个参数是否相等。另外,我们可以去掉event.preventDefault();

it("onChange param is the same value as the input element's value property", () => 
    const mockFn = jest.fn();
    const input = enzyme.shallow(<InputBox 
                                    value=""
                                    placeholder="" 
                                    className="" 
                                    onSearch=mockFn/>);

    input.find('input').simulate('change', target: value: 'matched' );
    expect(mockFn.mock.calls[0][0]).toBe('matched');
);

【讨论】:

以上是关于在 Jest 中测试 onChange 函数的主要内容,如果未能解决你的问题,请参考以下文章

React 测试库未在快照中显示输入 onChange 道具

Jest - 测试在反应变量中声明的 Vue.js 函数

在 Jest 测试框架中访问 Angular 服务构造函数

使用 Jest 在 Nodejs 中测试 Azure 函数 - process.env 变量显示未定义

快照测试的工作原理以及 toMatchSnapshot() 函数在 React 组件的 Jest 快照测试中的作用是啥?

使用 Jest 进行测试的共享 utils 函数