Skip to content

Instantly share code, notes, and snippets.

@mauricedb
Last active December 7, 2023 14:46
Show Gist options
  • Star 66 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save mauricedb/eb2bae5592e3ddc64fa965cde4afe7bc to your computer and use it in GitHub Desktop.
Save mauricedb/eb2bae5592e3ddc64fa965cde4afe7bc to your computer and use it in GitHub Desktop.
Testing stateful React hooks
import { useState } from 'react';
export function useCounter(initial = 0) {
const [count, setCount] = useState(initial);
return [count, () => setCount(count + 1)];
}
import { useCounter } from './Calculator';
const mockSetState = jest.fn();
jest.mock('react', () => ({
useState: initial => [initial, mockSetState]
}));
test('Can increment from 1 to 2', () => {
const [_, increment] = useCounter(1);
increment();
expect(mockSetState).toHaveBeenCalledWith(2);
});
@jdnichollsc
Copy link

Thanks for sharing!

@wasnio
Copy link

wasnio commented Jul 15, 2019

Thanks man!

@pingfengafei
Copy link

Thanks a looooooooooooooooooooooot.

@marco-souza
Copy link

marco-souza commented Jan 29, 2020

Nice, but we can't do this if we're testing React Components which uses useState. Any suggestion about how to do that?

@mauricedb
Copy link
Author

@marco-souza True but this trick is intended for testing hooks directly, not for testing components or hooks through a component. Use something like react-testing-library for that.

@marco-souza
Copy link

I see. So, I already solved that by using .spyOn in useState. Works like a charm 😸

@TempD
Copy link

TempD commented Feb 21, 2020

FYI this helped me to achieve test a react function component using useState:

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

jest.mock("react", () => ({
  ...jest.requireActual("react"),
  useState: jest.fn()
}));
 
// And before rendering (omitting var declarations for brevity) 
useStateMock.mockImplementation(initState => [initState, jest.fn()]);
component = shallow(<DetailPage {...mockProps} />);

This link helped me a bunch (go down to comments to see this solution): https://dev.to/theactualgivens/testing-react-hook-state-changes-2oga

@jbolotin
Copy link

The following technique works well for me testing functional components with useState destructured:

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

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

@tiago-lp
Copy link

tiago-lp commented Jun 4, 2020

@marco-souza how you solved using spyOn. You have any example? Thanks

@marco-souza
Copy link

marco-souza commented Jul 20, 2020

@marco-souza how you solved using spyOn. You have any example? Thanks

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

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

describe('testing useState mocked', () => {
  const setState = jest.fn();
  const useStateMock = (initState: any) => [initState, setState];

  jest.spyOn(React, 'useState').mockImplementation(useStateMock);

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

  // your tests goes here
});

@jdnichollsc
Copy link

For me it's better having this logic in a custom hook and using this library for the tests instead https://github.com/testing-library/react-hooks-testing-library

@muhsalaa
Copy link

muhsalaa commented Jul 29, 2020

how to spy specific useState hook, when I have more than one useState hook inside my component?

@jbolotin
Copy link

@muhsalaa, suppose you have a component like this:

const Component = () => {
    const setState1 = useState(1);
    const setState2 = useState(2);
    ...
};

In the test, you'll want to do:

import * as React from 'react';
...
it('Should work', () => {
    const useStateSpy = jest.spyOn(React, 'useState')
    ...
    expect(useStateSpy).toHaveBeenNthCalledWith(2, 2); // if testing the second `useState`
});

@DamengRandom
Copy link

not working ...

@rswnn
Copy link

rswnn commented Apr 8, 2023

How about
setState(prevState => ({ ...prevState, }))
?

@manishprakharan067
Copy link

manishprakharan067 commented Apr 24, 2023

How about
setState(prevState => ({ ...prevState, }))
?

Yeah What about class component How do I Change class setState ?

@bogdanq
Copy link

bogdanq commented Jul 13, 2023

how mock many state in useState?

not working

 jest.spyOn(React, 'useState')
    .mockImplementationOnce(() => [false, () => null])
    .mockImplementationOnce(() => [true, () => null]) 

@sjpox
Copy link

sjpox commented Nov 16, 2023

You may consider using renderHook (testing-library/react). I find it easy to use.

Here is the sample code:

const { result } = renderHook(() => {
      const [isOpen, setIsOpen] = useState(true)
      return { isOpen, setIsOpen }
    })

    const renderValue = await act(() => {
      return render(
        <ProductFormDialog
          isOpen={result.current.isOpen}
          setIsOpen={result.current.setIsOpen}
        />
      )
    })

    const displayValue = renderValue.getByTestId('header-container').textContent
    expect(displayValue).toEqual('Header')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment