@jedmao/redux-mock-store

A mock store for testing your redux async action creators and middleware

Usage no npm install needed!

<script type="module">
  import jedmaoReduxMockStore from 'https://cdn.skypack.dev/@jedmao/redux-mock-store';
</script>

README

@jedmao/redux-mock-store

GitHub Actions codecov npm version

A mock store for testing Redux async action creators and middleware. The mock store will create an array of dispatched actions which serve as an action log for tests.

This is a TypeScript fork of redux-mock-store.

Please note that this library is designed to test the action-related, not reducer-related logic (i.e., it does not update the Redux store). If you want a complex test combining actions and reducers together, take a look at other libraries (e.g., redux-actions-assertions). Refer to issue redux-mock-store#71 for more details.

Installation

npm install @jedmao/redux-mock-store --save-dev

Or

yarn add @jedmao/redux-mock-store --dev

Usage

You will benefit from configuring a single mockStore for all of your tests. A common example would be to configure your store with a redux-thunk middleware.

mockStore.js

import { configureMockStore } from '@jedmao/redux-mock-store'
import thunk from 'redux-thunk'

const middlewares = [thunk]

export default configureMockStore(middlewares)

Let's do the same thing in TypeScript and add an extra thunk argument.

mockStore.ts

import { configureMockStore } from '@jedmao/redux-mock-store'
import thunk, { ThunkDispatch } from 'redux-thunk'

// internal dependencies
import RootState from 'store/RootState'
import RootActions from 'actions'

const extraThunkArgument = { foo: 'bar' }
const middlewares = [thunk.withExtraArgument(extraThunkArgument)]

export default configureMockStore<
  RootState,
  RootActions,
  ThunkDispatch<RootState, typeof extraThunkArgument, RootActions>
>(middlewares)

Synchronous actions

The mock store saves all the dispatched actions inside the store instance. You can get all the actions by calling store.getActions().

import { mockStore } from 'utils/test'

describe('todo actions', () => {
  let store: ReturnType<typeof mockStore>

  beforeEach(() => {
    store = mockStore(/* initial state */)
  })

  it('dispatches ADD_TODO', () => {
    const action = { type: 'ADD_TODO' }

    store.dispatch(action)

    expect(store.getActions()[0]).toBe(action)
  })
})

Asynchronous actions

it('asynchronously dispatches SUCCESS', async () => {
  const store = mockStore(/* initial state */)
  const success = { type: 'SUCCESS' }

  await store.dispatch(async dispatch => {
    dispatch(success)
  })

  expect(store.getActions()[0]).toBe(success)
})

See the tests for more thorough examples.

API

configureMockStore

Configure the mock store by applying middlewares.

configureMockStore<
  S = any,
  A extends Redux.Action = Redux.AnyAction,
  DispatchExts extends {} | void = void
>(
  middlewares: Redux.Middleware[] = [],
): MockStoreCreator<S, A, DispatchExts>

Calling configureMockStore will return a MockStoreCreator, which returns an instance of the configured mock store. This MockStoreCreator is a function named mockStore.

mockStore

Call this function to reset your store after every test.

function mockStore(
  getState: S | MockGetState<S> = {} as S,
): DispatchExts extends void
  ? MockStore<S, A>
  : MockStoreEnhanced<S, A, DispatchExts>

Mock Store API

dispatch

Dispatches an action T through the mock store. The action will be stored in an array inside the instance and executed.

dispatch<T extends A>(action: T): T

If DispatchExts are provided, dispatch will support an additional signature.

dispatch<R>(
  asyncAction: ThunkAction<R, S, E, A>,
): R

getState

Returns the state S of the mock store.

getState(): S

getActions

Returns the actions A[] of the mock store.

getActions(): A[]

clearActions

Clears the stored actions.

clearActions(): void

subscribe

Subscribe a listener to the store.

subscribe(
  listener: (action: A) => void,
): Redux.Unsubscribe

replaceReducer

Because a mock store does not have or support reducers, this function will always throw an error.

replaceReducer(nextReducer: Reducer<S, A>): never

Mock stores do not support reducers. Try supplying a function to getStore instead.

TypeScript

The @jedmao scoped version of this library is written in TypeScript and published with generated type information. No need to npm install additional @types. Additionally, a number of types and interfaces (below) have been exported for your convenience.

MockStoreCreator

If you provide DistpatchExts then this type will return a MockStoreEnhanced, which supports async actions (e.g., thunks). Otherwise, it will just return a plain ol' MockStore for sync actions.

type MockStoreCreator<
  S = {},
  A extends Action = AnyAction,
  DispatchExts extends {} | void = void
> = (
  state?: S | MockGetState<Redux.DeepPartial<S>>,
) => DispatchExts extends void
  ? MockStore<S, A>
  : MockStoreEnhanced<S, A, DispatchExts>

MockGetState

This type is used in the MockStoreCreator via state?: S | MockGetState<S>, which allows you to either supply a single state object S or a function that would return S. Why a function? See redux-mock-store#102.

type MockGetState<S = {}> = (actions: AnyAction[]) => S

MockStoreEnhanced

Enables async actions (e.g., thunks).

type MockStoreEnhanced<
  S,
  A extends Action = AnyAction,
  DispatchExts = {}
> = MockStore<Redux.DeepPartial<S>, A> & {
  dispatch: DispatchExts
}

MockStore

interface MockStore<S = any, A extends Redux.Action = Redux.AnyAction>
  extends Redux.Store<Redux.DeepPartial<S>, A> {
  clearActions(): void
  getActions(): A[]
  subscribe(listener: (action: A) => void): Redux.Unsubscribe
}

License

The MIT License