Mocking redux useSelector-hook
Tags: react, redux, testing, jest
Published: 2020-03-19 22:54:57


_*Update*_
_There is an official way of using [RTL with redux](https://testing-library.com/docs/example-react-redux#__docusaurus) as some people pointed out in the comments but I never got it to work.
It may be me being incompetent or something in my project that causes issues so my solution to only mock `useSelector` may still be of use._ 🙄

Recently I finally made the switch from [Enzyme](https://enzymejs.github.io/enzyme/) to [React testing library (RTL)](https://github.com/testing-library/react-testing-library) which also means that instead of rendering components using `shallow` like Enzyme proposes, with React testing library the whole component and its child components is rendered, much like Enzymes `mount`.

The switch to RTL coupled with using hooks instead of HOCs when using Redux got me writing a lot of new component tests but I did run in to some problem when I tried to use the `useSelector`-hook from Redux multiple times expecting different responses.

The component that I wanted to test as a search component that made calls similar to this:

```javascript
const MySearchComponent = () => {
  const { query, rows } = useSelector((state) => 
    state.config);

  const {
      items,
      hasMore
    } = useSelector((state) => state.search);

  return (...)
}
```

`useSelector` takes a callback function that takes the state as an argument and returns a slice of the state.

So my first approach when trying to test the component was to send two different responses.

```javascript
jest.mock("react-redux", () => ({
  ...jest.requireActual("react-redux"),
  useSelector: jest.fn()
    .mockReturnValueOnce(mockConfigState)
    .mockReturnValueOnce(mockSearchState)
}));

describe("MySearchComponent", () => {
  afterEach(() => {
    useSelector.mockClear();
  });
  it("should render", () => {
    const { getByTestId } = render(<MySearchComponent />);
    expect(...)
  });
});

``` 

Which worked fine until I realised that a child component also calls useSelector and therefore crashed. 😱

I knew I needed something that would support all possible selectors that I needed but still could be modified on a test by test basis. 
I had a mock state ready, but not the method to alter and inject it. 
Until I ran across `jest.fn().mockImplementation`...

## The solution to my problems

`useSelector` takes a callback as its argument and all I had to do was to call that callback with a compatible state that would satisfy all my components needs and they would do the rest as implemented.

```javascript
jest.mock("react-redux", () => ({
  ...jest.requireActual("react-redux"),
  useSelector: jest.fn()
}));

describe("MySearchComponent", () => {
  beforeEach(() => {
    useSelector.mockImplementation(callback => {
      return callback(mockAppState);
    });
  });
  afterEach(() => {
    useSelector.mockClear();
  });
  it("should render a query", () => {
    const { getByTestId } = render(<MySearchComponent />);
    expect(getByTestId("query_testId").textContent)
      .toEqual(mockAppState.config.query)
  });
  it("should not render if query is empty", () => {
      const localMockState = {
        ...mockAppState,
        config: {
          ...mockShoppingState.config,
          query: ""
        }
      };
      useSelector.mockImplementation(callback => {
        return callback(localState);
      });
    const { queryByTestId } = render(<MySearchComponent />);
    expect(queryByTestId("query_testId")).toBeNull();
  });
});

``` 

So in the code above I mock `useSelector` from the react-redux npm package and replaces it with a function that executes any given callback function with my mocked state as an argument. This is done before every test.

In the second test I create a second mocked state that I want to use for just that test so I override `useSelector` to make sure it uses my updated state instead of the default mock state.  

## Parting words

I hope this helped someone to learn a little bit more about how to test their code and what can be achieved with jest and tools like RTL (which is great, try it!)

All typos my own and please leave a comment if you have a question or something does not make sense.