Search code examples
reactjstypescriptjestjsreact-testing-libraryredux-toolkit

Test redux-toolkit component with custom renderWithProviders failing


I want to create a couple of tests for my components, but since I'm using Redux I need to send a provider, according to the documentation I need to do something like a renderWithProviders so I can re use it for different components and that's what I'm trying to do, I'm using Vite and TypeScript for this project, but I'm getting the following errors and I don't know how to fix it.

enter image description here

This is the custom render that I'm trying to create:

import React, { PropsWithChildren } from 'react'
import { render } from '@testing-library/react'
import { configureStore } from '@reduxjs/toolkit'
import { Provider } from 'react-redux'

import favsReducer from '../../store/favs/slice'

export function renderWithProviders(
  ui: React.ReactElement,
  {
    preloadedState = {},
    // Automatically create a store instance if no store was passed in
    store = configureStore({
      reducer: { favs: favsReducer },
      preloadedState,
    }),
    ...renderOptions
  } = {}
) {
  function Wrapper({ children }: PropsWithChildren) {
    return <Provider store={store}>{children}</Provider>;
  }

  // Return an object with the store and all of RTL's query functions
  return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}

My store is this one:

import { Middleware, configureStore } from "@reduxjs/toolkit"
import favsReducer from './favs/slice'

const persistanceLocalStorageMiddleware: Middleware = (store) => (next) => (action) => {
  next(action);
  localStorage.setItem('__favorite__redux__state__', JSON.stringify(store.getState()));
};

export const store = configureStore({
  reducer: {
    favs: favsReducer
  },
  middleware: (getDefaultMiddleware) => {
    return getDefaultMiddleware().concat(persistanceLocalStorageMiddleware)
  }
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

and the slice for favorites is this one:

import { createSlice, PayloadAction } from "@reduxjs/toolkit"
import { Favorite, Id, FavoriteWithId } from "../../interfaces/FavoritesInterface"

const DEFAULT_STATE: FavoriteWithId[] = []

const initialState: FavoriteWithId[] = (() => {
  const persistedState = localStorage.getItem('__favorite__redux__state__')
  if (persistedState) return JSON.parse(persistedState).favs
  return DEFAULT_STATE
})()

export const favsSlice = createSlice({
  name: 'favs',
  initialState: initialState,
  reducers: {
    addNewFavorite: (state, action: PayloadAction<Favorite>) => {
      const id = crypto.randomUUID();
      return [...state, { id, ...action.payload }]
    },
    deleteFavoriteById: (state, action: PayloadAction<Id>) => {
      const id = action.payload;
      return state.filter((fav) => fav.id !== id)
    }
  }
})

export default favsSlice.reducer
export const { addNewFavorite, deleteFavoriteById } = favsSlice.actions

So, I don't know why I'm getting the message

'favs' does not exist in type 'Reducer<{ favs: FavoriteWithId[]; }, UnknownAction, any>'

I thought the reducer in the tests should have the 'favs' property as the one on the store I configure but I'm not sure anymore, can someone provide me a clue or something on how to proceed?

The unit test that I'm trying to write is this one:

import '@testing-library/jest-dom/jest-globals'
import '@testing-library/jest-dom'
import { screen } from '@testing-library/react'
import DogCard from '../components/profile-card/DogCard'
import { renderWithProviders } from './utils/test-utils'

describe('DogCard component', () => {
  test('renders the DogCard component', () => {
    renderWithProviders(<DogCard />)
    const dogImage = screen.getByTestId('dog-image')
    expect(dogImage).toBeInTheDocument()
  })
})

Here is the github repo


Solution

  • This fix it

    const preloadedState: FavoriteWithId[] = []
    
    export function renderWithProviders(
      ui: React.ReactElement,
      {
        // Automatically create a store instance if no store was passed in
        store = configureStore({
          reducer: favsReducer,
          preloadedState,
        }),
        ...renderOptions
      } = {}
    ) {
      function Wrapper({ children }: PropsWithChildren) {
        return <Provider store={store}>{children}</Provider>;
      }
    
      // Return an object with the store and all of RTL's query functions
      return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
    }