DEV Community

Mocking redux useSelector-hook

Fredrik Bergqvist on March 19, 2020

Update There is an official way of using RTL with redux as some people pointed out in the comments but I never got it to work. It may be me being i...
Collapse
 
murilovarela profile image
Murilo Varela

In the RTL documentation, you can find an explanation of how you can wrap your component with the store provider. (testing-library.com/docs/example-r...)

I like to test whether my actions are called or not, and also pass a custom initialState. The aproach I use is the following above:

export function renderWithRedux(ui, { initialState = initialStateOriginal } = {}) {
  const actions = [];
  const observerMiddleware = () => next => action => {
    actions.push(action);
    return next(action);
  };
  const store = createStore(reducer, initialState, applyMiddleware(observerMiddleware));
  const utils = {
    dispatch(action) {
      return store.dispatch(action);
    },
    getDispatchedActions() {
      return actions;
    },
    getState() {
      return store.getState();
    },
  };
  return {
    ...render(<Provider store={store}>{ui}</Provider>),
    ...utils,
  };
}
Enter fullscreen mode Exit fullscreen mode

So you can get the dispatch, getDispatchedActions, and getState from the render result.

const { getByText, getDispatchedActions, getAllByTestId, dispatch } = renderWithRedux(
      <MyComponent />,
      { initialState: {......} }
    );
Enter fullscreen mode Exit fullscreen mode
Collapse
 
fredrikbergqvist profile image
Fredrik Bergqvist

Very nice, thanks for the suggestion!

Collapse
 
gugol2 profile image
Watchmaker

When I do this, my calls to the components inside MyComponent get called just twice as many times as before using useSelector...

Somehow useSelector is provoking more re-renders.

Collapse
 
gugol2 profile image
Watchmaker • Edited

I found why...
Selectors have to return only one single field of the state that can be ONLY calculated used itself and no other slices of the state.
Otherwise you have to use Reselect or another library to create memoized selectors that compute and/or return multiple fields of the state.

My experience is that if your operations on the state are a bit complex maybe useSelector adds a bit too much complexity for what if offers and in that case it is maybe better stay with the connect HOC and its mapStateToProp function to operate on the state.
But if your operations on the state are simple useSelector may clear up your component a bit.

Collapse
 
ajinkyax profile image
Ajinkya Borade

you forgot to mention about Render.

import { render as rtlRender } from '@testing-library/react'
...

function render (
  ui,
  {
    initialState,
    store = configureStore({
      reducer: {
        env: envReducer
      }
    }),
    ...renderOptions
  } = {}
) {
  const Wrapper = ({ children }) => (
    <Provider store={store}>{children}</Provider>
  )
  return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
gokulmathew profile image
gokulmathew

I am getting this error.
TypeError: _reactReedux.useSelector.mockImplementation is not a function.
Kindly someone help

Collapse
 
mikeour profile image
Mike Roeslein

Could try casting it as a mock: (useSelector as jest.Mock).mockImplementation(...)

Collapse
 
gokulmathew profile image
gokulmathew

Tried that also dude, but didnt work. Added how to test useDispatch of react hooks? using react testing library.

Thread Thread
 
fredrikbergqvist profile image
Fredrik Bergqvist

It does not seem like redux is being mocked (looking for mockImplementation in react redux)

In the code below I've listed all moving parts of the test

  1. You need to import the react-redux library
  2. You need to mock the function
  3. You need to add the mockImplementation to provide a response value

Having all three pieces should make it work.

import { useSelector } from "react-redux";

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

describe("MySearchComponent", () => {
  beforeEach(() => {
    useSelector.mockImplementation(callback => {
      return callback(mockAppState);
    });
  });
  test(() => {
    // Your test here
  });
});
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
anshumanburman profile image
anshumanburman

what is "mockAppState"? can you please explain me more?

Thread Thread
 
fredrikbergqvist profile image
Fredrik Bergqvist

mockAppState is the mocked redux state that your component needs to be able to run.
It should include data for all redux nodes that the component is using.

Take a component like this:

//the redux state looks like this;
// {
//   name: "Fredrik",
//   title: "Developer"
// }
import { useSelector } from "react-redux";
const MyComponent = () => {
  const reduxState = useSelector((state) => state)
  return (<h1>Hello {reduxState.name}</h1>)
}
Enter fullscreen mode Exit fullscreen mode

A mocked app state for the above component would look like this:

{
  name: "Mocked name",
 }
Enter fullscreen mode Exit fullscreen mode

So that you can run tests against a state that you have full control over.

Collapse
 
wolverineks profile image
Kevin Sullivan

Would it be the same/simpler to render the component in a ?

Collapse
 
fredrikbergqvist profile image
Fredrik Bergqvist

I think you might have forgot a word there :)

In Enzyme I would wrap the component in a <provider> but RTL did not allow that and I find it cleaner and more explicit to mock useSelector.

Collapse
 
markerikson profile image
Mark Erikson • Edited

What do you mean by "did not allow that"?

RTL's docs specifically show how to use it with React-Redux:

function renderWithRedux(
  ui,
  { initialState, store = createStore(reducer, initialState) } = {}
) {
  return {
    ...render(<Provider store={store}>{ui}</Provider>),
    // adding `store` to the returned utilities to allow us
    // to reference it in our tests (just try to avoid using
    // this to test implementation details).
    store,
  }
}

// then, in a test:
const { getByTestId, getByText } = renderWithRedux(<Counter />)
Thread Thread
 
fredrikbergqvist profile image
Fredrik Bergqvist

Well to be honest I did add it to the render method, noticed that I got an error and took the other way out :)

Thread Thread
 
timrohrer profile image
tim.rohrer

Another dev (way more experienced than me) also mocks useSelector.

I would like to see the "official" way work, but so far I'm still stuck.

In my component, a useSelector that includes the callback definition works. However, any useSelector that requires an imported callback seems to remain undefined.

This works:

  let demoTrip = useSelector(
    (state: RootState) => state.trips.allTrips[demoTripId]
  );

This returns undefined:

import { selectChosenMappingService } from '../system/systemSlice';  
...
const mappingServiceName = useSelector(selectChosenMappingService);

And from my slice:

export const selectChosenMappingService = (
  state: SystemState
): MappingService => state.chosenMappingService;

I remain confused.

Thread Thread
 
timrohrer profile image
tim.rohrer

I am happy to report that my issue was a mistake in my definitions of my selector functions in my slice. Turns out I needed to type as RootState and then include the reducer in the return (i.e., state.system.chosenMappingService).

Collapse
 
lxuanchengdx profile image
lxuanchengdx

Hey Fredrik, thank a lot! it really helps me right now.

Collapse
 
fredrikbergqvist profile image
Fredrik Bergqvist

Thank you! Glad it could be of some help :)

Collapse
 
joaodos3v profile image
João Vitor Veronese Vieira

It saved my time! Thank you :)

Collapse
 
thisthat90 profile image
thisthat

Instead of requireActual, can we use Jest.spyOn? For me, the below code seems to work fine.

jest
.spyOn(Redux, "useSelector")
.mockImplementation((callback) => callback(mockAppState));