【问题标题】:Testing onChange function in Jest在 Jest 中测试 onChange 函数
【发布时间】: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

【问题讨论】:

    标签: reactjs jestjs enzyme


    【解决方案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');
          });
          

          【讨论】:

            猜你喜欢
            • 2020-03-08
            • 2018-10-21
            • 2020-03-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-01-08
            • 2019-07-22
            • 2021-08-08
            相关资源
            最近更新 更多