How to mock the useState hook implementation so that it actually changes the state during testing

22,558

you should probably not test the internal implementation (e.g. the state in useState etc), but only test the external functionality (click on button changes output).

This make it easier to test your code, you actually test what you want to test and not how it is implemented and if you change the implementation (rename variable for example) you will get a false negative because the code works fine (does not care for variable name and the correct component will be rendered) but your tests will fail, because you changed the name of a variable for example.

This will make it more cumbersome to fix your tests later if you change the code. If you have a large codebase, you want to know if your code works and not how it is implemented.

Hope this helps. Happy coding.

Share:
22,558
Dylanbob211
Author by

Dylanbob211

Updated on October 02, 2020

Comments

  • Dylanbob211
    Dylanbob211 about 3 years

    I'm trying to test a component which renders two different sub-components when its internal state changes from false to true: when it's false it renders a button that, if pressed, changes the state from false to true and renders the other one. The other is a form that on submit does the opposite.

    I've tried to spy on the useState hook to test if it's actually called. But by mocking the module, the actual setState won't work when I need it in the second part of the test, to test the form that renders after.

    This is my component:

    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;
    

    This is my test:

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

    I know what's the problem, but I don't know how to fix it. Is there a way to mock setState? Or is there a way to split the tests so that they don't interfere with each other?

  • Dylanbob211
    Dylanbob211 about 4 years
    Well, I'm trying to figure out what to test in a component. You're probably right but from a purely scientific point of view is it possible to do what I would like to do? I've found on the jest documentation about bypassing module mocks, here jestjs.io/docs/en/bypassing-module-mocks, but I can't figure out how to solve that problem
  • Domino987
    Domino987 about 4 years
    I actually don't think so. Here check this out how Kent C Dodds tests his components.
  • Petro Ivanenko
    Petro Ivanenko almost 3 years
    Also, if you have multiple useState hooks, your mocks (of useState) may give false negatives in case someone decides to simply reorder useState hook calls.