如何模拟 useState 钩子实现,以便它在测试期间实际更改状态

IT技术 javascript reactjs jestjs enzyme
2021-04-28 00:18:48

我正在尝试测试一个组件,该组件在其内部状态从 false 变为 true 时呈现两个不同的子组件:当它为 false 时,它​​呈现一个按钮,如果按下该按钮,则将状态从 false 更改为 true 并呈现另一个。另一种是在提交时执行相反操作的表单。

我试图监视 useState 钩子以测试它是否被实际调用。但是通过模拟module,当我在测试的第二部分需要它来测试之后呈现的表单时,实际的 setState 将不起作用。

这是我的组件:

import React, { useState } from 'react';

const MyComponent = ({handleChange, handleInput}) => {
     const [state, setState] = useState(false);

     return (
       <div>
         {!state
           ? (
             <button
               data-test="button1"
               type="button"
               onClick={() => setState(true)}
             >
               RenderForm
             </button>
           )
           : (
             <form onSubmit={() => setState(false)}>
               <input type="text" onChange={e => handleChange(e)} />
               <button type="submit">
                 Submit Form
               </button>
               <button type="button" onClick={() => setState(false)}>
                 Go Back
               </button>
             </form>
           )
         }
       </div>
     );
   };
export default MyComponent;

这是我的测试:

import React from 'react';
import { mount } from 'enzyme';
import MyComponent from './MyComponent';


describe('MyComponent', () => {
    let component;

    const mockChange = jest.fn();
    const mockSubmit = jest.fn();
    const setState = jest.fn();
    const useStateSpy = jest.spyOn(React, 'useState');
    useStateSpy.mockImplementation(init => [init, setState]);

    beforeEach(() => {
       component = mount(<MyComponent handleChange={mockChange} handleSubmit={mockSubmit}/>);
    });

    afterEach(() => {
      component.unmount();
    });

    it('calls setState when pressing btn', ()=> {
       component
         .find('[data-test="button1"]')
         .simulate('click')
       expect(setState).toHaveBeenCalledWith(true) // passes
    })
    it('calls handleChange when input changes value', () => {
       component
         .find('[data-test="button1"]') //can't be found
         .simulate('click') 
       component
         .find('input')
         .simulate('change', { target: { value: 'blabla' }}) 
       expect(mockChange).toHaveBeenCalled() // doesn't pass  
  })

  });

我知道是什么问题,但我不知道如何解决它。有没有办法模拟 setState?或者有没有办法拆分测试,这样它们就不会相互干扰?

1个回答

您可能不应该测试内部实现(例如 useState 中的状态等),而应该只测试外部功能(单击按钮更改输出)。

这使得测试您的代码更容易,您实际上测试的是您想要测试的内容而不是它的实现方式,如果您更改实现(例如重命名变量),您将得到一个假阴性,因为代码工作正常(不在乎对于变量名称,将呈现正确的组件)但您的测试将失败,因为例如您更改了变量的名称。

如果您更改代码,这将使以后修复测试变得更加麻烦。如果您有一个大型代码库,您想知道您的代码是否有效,而不是它是如何实现的。

希望这可以帮助。快乐编码。