How to mock store in redux toolkit [duplicate]
Asked Answered
W

4

14
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import { render, screen, fireEvent } from '@testing-library/react';
import MyApp from './MyApp ';

const initialState = {};
const mockStore = configureStore(initialState);

describe('<MyApp />', () => {
  it('click button and shows modal', () => {
    render(
      <Provider store={mockStore}>
        <MyApp />
      </Provider>
    );

    fireEvent.click(screen.getByText('ADD MIOU'));
    expect(queryByText('Add MIOU Setting')).toBeInTheDocument();
  });
});

I am using jest and redux toolkit with reactjs, and trying to mock a store to write a test. But got the following error

TypeError: store.getState is not a function

Is there any way to fix this? Am I missing something?

Warhead answered 20/11, 2020 at 8:5 Comment(1)
See medium.com/@lucksp_22012/…Withoutdoors
E
12

I assume you are trying to test a connected component, and you expect (1) action creators and reducers to be run and (2) redux state to be updated as part of your test?

I have not used redux-mock-store, but I see the following note on their documentation, which leads me to believe this library may not work the way you expect:

Please note that this library is designed to test the action-related logic, not the reducer-related one. In other words, it does not update the Redux store.

I suggest you try this approach for testing connected components. I have used this approach to write tests that update redux state and render connected components.

First, you override the RTL render method:

// test-utils.js
import React from 'react'
import { render as rtlRender } from '@testing-library/react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
// Import your own reducer
import reducer from '../reducer'

function render(
  ui,
  {
    initialState,
    store = createStore(reducer, initialState),
    ...renderOptions
  } = {}
) {
  function Wrapper({ children }) {
    return <Provider store={store}>{children}</Provider>
  }
  return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
}

// re-export everything
export * from '@testing-library/react'
// override render method
export { render }

Then you reference that new render method instead of RTL directly. You can also provide initial state for your test.

import React from 'react'
// We're using our own custom render function and not RTL's render
// our custom utils also re-export everything from RTL
// so we can import fireEvent and screen here as well
import { render, fireEvent, screen } from '../../test-utils'
import App from '../../containers/App'

it('Renders the connected app with initialState', () => {
  render(<App />, { initialState: { user: 'Redux User' } })

  expect(screen.getByText(/redux user/i)).toBeInTheDocument()
})

(All code copied from redux.js.org.)

Esperance answered 20/11, 2020 at 15:18 Comment(2)
I'm trying to do this in typescript however I cannot find the correct types to use for the test-utils.js file. Have you tried this in typescript?Lucianolucias
@PaulRyanLucero check the linked docs, it now contains a ts version.Hosey
B
4

As of January 2023 it is no longer recommended to mock the store in redux, see the docs here and this answer for more information.

Blunk answered 6/1, 2023 at 17:36 Comment(0)
T
2

I was having the same problem as you but thanks to @srk for linking the Redux docs and, the React Testing Library docs, I found a pretty good solution that worked for me with TypeScript:

// store.ts - just for better understanding
export const store = configureStore({
  reducer: { user: userReducer },
})

export type RootState = ReturnType<typeof store.getState>
// test-utils.ts
import React, { ReactElement } from 'react'
import { Provider } from 'react-redux'
import { render as rtlRender, RenderOptions } from '@testing-library/react'
import {
  configureStore,
  EmptyObject,
  EnhancedStore,
  PreloadedState,
} from '@reduxjs/toolkit'

// import your reducers
import userReducer from 'features/user/user.slice'

import type { RootState } from 'app/store'

// ReducerTypes is just a grouping of each slice type,
// in this example i'm just passing down a User Reducer/State.
// With this, you can define the type for your store.
// The type of a configureStore() is called EnhancedStore,
// which in turn receives the store state as a generic (the same from store.getState()).
type ReducerTypes = Pick<RootState, 'user'>
type TStore = EnhancedStore<ReducerTypes>

type CustomRenderOptions = {
  preloadedState?: PreloadedState<ReducerTypes & EmptyObject>
  store?: TStore
} & Omit<RenderOptions, 'wrapper'>

function render(ui: ReactElement, options?: CustomRenderOptions) {
  const { preloadedState } = options || {}

  const store =
    options?.store ||
    configureStore({
      reducer: {
        user: userReducer,
      },
      preloadedState,
    })

  function Wrapper({ children }: { children: React.ReactNode }) {
    return <Provider store={store}>{children}</Provider>
  }

  return rtlRender(ui, { wrapper: Wrapper, ...options })
}

// re-export everything
export * from '@testing-library/react'
// override render method
export { render }

Then you just have to pass down an object with the preloadedState property as the second parameter to your render; you can even define a new store inside the render if you want with the "store" property.

describe('[Component] Home', () => {
  it('User not logged', () => {
    const component = render(<Home />)
    expect(component.getByText(/User is: undefined/i)).toBeInTheDocument()
  })

  it('User logged in', () => {
    const component = render(<Home />, {
      preloadedState: { user: { name: 'John' /* ...other user stuff */ } },
    })
    expect(component.getByText(/User is: John/i)).toBeInTheDocument()
  })
})
Transcendentalism answered 4/6, 2022 at 20:0 Comment(0)
T
1

I couldn't find anywhere else to paste my findings regarding redux toolkit and redux-mock-store.

In order to dispatch async thunks and test results you can specify the type of dispatch when creating the mock store.

import configureStore from 'redux-mock-store';
import { getDefaultMiddleware } from '@reduxjs/toolkit'

const middlewares = getDefaultMiddleware();
const mockStore = createMockStore<IRootState, AppDispatch>(middlewares);

describe('my thunk action', () => {
  const store = mockStore();

  test('calls my service', async() => {
    await store.dispatch(myThunk({ id: 32 }));

    expect(myService).toBeCalledWith({ id: 32 });
  });

  test('contains foo bar actions', async() => {
    await store.dispatch(myThunk({ id: 32 }));

    expect(store.getActions()).toEqual(....);
  });
});
Taoism answered 17/6, 2021 at 9:34 Comment(1)
getDefaultMiddleware() is deprecated, do we need to explicitly use the redux-thunk package for the async middleware? @TaoismWunder

© 2022 - 2024 — McMap. All rights reserved.