DEV Community

Discussion on: Testing React Hook State Changes

Collapse
 
casconed profile image
Jason Moore •

This is super helpful, thank you. Can you shed any light on why importing useState:

import React, { useState } from 'react'

and calling it on line 4 of FunctionalComponent.jsx as:

const [count, setCount] = useState(0)

breaks the test? Seems like the spy should still see that getting called?

Collapse
 
theactualgivens profile image
Andrew Givens •

Yup, so the issue is because we are spying on React.useState, which sets a spy on the default export from 'react'. Since you are pulling in useState as a named export, there is no spy on it.

I've messed around with mocking the named export, but it's not super straight forward. I've always done it using import * as Something from 'module';, but I can't seem to get that to work with react.

And if you mock the whole react module, you get some errors from enzyme.

Here are some of the sources I used when looking into this:
jest-mock-default-named-export
mock-spy-exported-functions-within...

If you can figure it out, please let me know because I'm very interested :)
But it seems that just calling useState from React is the easiest way. To be honest, I don't see the point in importing useState as a named export when it's already included in the default export.

Collapse
 
casconed profile image
Jason Moore •

Great, thanks. I'll play around with it - we're using useState as a named export all over the place - wouldn't be a HUGE deal to convert but it'd be nice to figure out how to test as-is.

Thread Thread
 
squidsoup profile image
Kit Randel •

Did you happen to find a solution?

Thread Thread
 
ppciesiolkiewicz profile image
Piotr •

to make it work with useState we would have to mock it using jest while keeping rest of react intact. jest.requireActual can be used to achieve that

import React, { useState as useStateMock } from 'react';

jest.mock('react', () => ({
  ...jest.requireActual('react'),
  useState: jest.fn(),
}));

describe('Test', () => {
  const setState = jest.fn();

  beforeEach(() => {
    useStateMock.mockImplementation(init => [init, setState]);
  });

...
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
jbolotin profile image
Jonah Bolotin •

The following technique works well for me testing functional components with useState destructured. This is an adapted solution from that above because the mockImplementation above caused react-test-renderer tests to fail:

import * as React from 'react';

describe('Some message', () => {
    const setState = jest.fn();
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const useStateMock: any = (initState: any) => [initState, setState];

    afterEach(() => {
      jest.clearAllMocks();
    });

    it('Is a test where we want to mock useState', () => {
          jest.spyOn(React, 'useState').mockImplementation(useStateMock);
          const wrapper = shallow(<Component {...props} />);
          // trigger setState somehow
          expect(setState).toHaveBeenCalledTimes(1);
          // Other tests here
    });
});
Enter fullscreen mode Exit fullscreen mode

This is typescript, but it should work just as well in JS if you remove the type annotations

Thread Thread
 
fredreis profile image
Fred-Reis •

works fine for me

Thread Thread
 
brianle10680751 profile image
Brian Le •

Thanks @Jonah, It works

Thread Thread
 
dhanvinarc profile image
Dhanvin Patel • • Edited

How can we test this type of case with jest and enzyme?
const [open, setOpen] = useState(false);
const handleClose = () => {
setOpen(!open);

}
Enter fullscreen mode Exit fullscreen mode

this handleClose is an onClick event