0

I am having a lot of trouble trying to implement tests for a component using the useSelector hook from react redux. I've seen some questions already about this subject but I didn't manage to fix my problem using the suggested solutions to those questions.

My component is pretty big so I won't post it all but the part giving me trouble looks like this :

Total.tsx

import React from 'react';

import clsx from 'clsx';
import i18next from 'i18next';
import { useSelector } from 'react-redux';
import { Trans } from 'react-i18next';

import Box from '@material-ui/core/Box';
import CustomTooltip from '../CustomTooltip/CustomTooltip';
import SkeletonTotal from 'components/Skeletons/Total';

import { ApplicationHelper } from 'helpers';

import './Total.scss';

//Some interfaces here for types since this is in TypeScript

function Total(props: TotalProps) {
  const { currency } = useSelector(
    (state: { currencyReducer: any }) => state.currencyReducer
  );
...
}

I first tried to test it like another component that doesn't use redux like so :

Total.test.js (first attempt)

import React from 'react';
import Total from './Total';
import { render } from '@testing-library/react';

test('test', () => {
  const { container } = render(
    <Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />
  );
});

But I was getting an error saying I need a react-redux context value and to wrap my component in a Provider which led me to try this :

Total.test.js (attempt 2)

import React from 'react';
import { Provider } from 'react-redux'
import Total from './Total';
import { render } from '@testing-library/react';

test('test', () => {
  const { container } = render(
    <Provider>
      <Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />
    </Provider>
  );
});

I am now getting a "Cannot read property 'getState' of undefined" error for the Provider component. I did try to mock a store to pass to my Provider as well as using jest to mock a return value like so

const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({currency: 'cad'})

Unfortunately I was unsuccessful to make this work and could not find a working solution in the other questions that might relate to this. Any ideas how I could make this work? Thanks

3
  • In order to use useSelector you need to be inside of a Provider and the Provider need to have a store prop. So attempt 2 is going in the right direction. The docs explain a way to override the render function to render inside of a Provider: redux.js.org/recipes/writing-tests#connected-components Commented Feb 23, 2021 at 22:19
  • @LindaPaiste I did try to do as shown in the documentation but could not make it work. Would you mind giving me a starting approach with my current code? I'd really appreciate it as I've been stuck on this test class for a while. Commented Feb 23, 2021 at 22:30
  • codesandbox.io/s/testing-react-redux-useselector-j3ynk?file=/… Commented Feb 24, 2021 at 2:28

1 Answer 1

2

The useSelector hook relies on the redux Context in order to access the state, so it must be inside of a Provider component in order to work. Your second attempt is on the right track, but you haven't set the store prop on the Provider, so the store is undefined and you get error "Cannot read property 'getState' of undefined".

Since you'll likely have many components that you'll want to test with redux context, the redux docs suggest creating your own version of the react testing library's render function which wraps the element in a provider before rendering it. This new render function adds two new optional options to the standard RTL options: initialState and store.

You can basically copy and paste that entire test-utils.js example from the docs, but I modified the return to include the created store so that we can dispatch to it directly (rather than just interacting with the component in ways that will dispatch an action).

return {
  ...rtlRender(ui, { wrapper: Wrapper, ...renderOptions }),
  store
};

With typescript annotations.

Inside your component test file, you will use your test-utils to render the Total component. It's fine to return the container element but you don't actually need to because you can query matching elements on the global RTL screen object or on the bound queries for your base element. We are basically looking to see that the outputted HTML code matches the expectations. You could test the selector itself in isolation, but it seems like you are trying to test the component.

Your test might look something like this:

import React from "react";
import Total from "./Total";
import { render, screen } from "./test-utils";
// if you want events: import userEvent from "@testing-library/user-event";

test( 'gets currency from redux', () => {
  // render with an initial currency
  const { store, container, getByLabelText } = render(
    // not sure where these props come from, presumable constants in the file
    <Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />,
    { initialState: { currency: USD } }
  );
  // some sort of RTL matcher or document.querySelector
  const currencyElement = getByLabelText(/currency/i); // uses regex for case-insensitivity
  // some sort of check for value
  expect(currencyElement?.innerText).toBe("USD");

  // dispatch an action to change the currency
  // might want to wrap in `act`? 
  store.dispatch(setCurrency("EUR"));
  
  // check that the value changed
  expect(currencyElement?.innerText).toBe("EUR");
});

Working example that I created based on a basic counter component.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.