@render-with/redux
TypeScript icon, indicating that this package has built-in type declarations

3.0.0 • Public • Published

Render decorators 🪆 for Redux

GitHub Workflow Status Code Coverage npm (scoped) NPM PRs welcome All Contributors

Use one of these decorators if your component under test requires a Redux store:

  • withState(..)
  • withStore(..)

Example:

import { render, withState } from './test-utils'

it('shows loaded users', () => {
  render(<Users />, withState({ users: [ { name: 'John' } ] }))
  // ...
})

Note: Refer to the core library to learn more about how decorators can simplify writing tests for React components with React Testing Library.

Table of Contents

Installation

This library is distributed via npm, which is bundled with node and should be installed as one of your project's devDependencies.

First, install the core library with a render function that supports decorators:

npm install --save-dev @render-with/decorators

Next, install the Redux decorators provided by this library:

npm install --save-dev @render-with/redux

or

for installation via yarn:

yarn add --dev @render-with/decorators
yarn add --dev @render-with/redux

This library has the following peerDependencies:

npm peer dependency version
npm peer dependency version

and supports the following node versions:

node-current (scoped)

Setup

In your test-utils file, re-export the render function that supports decorators and the Redux decorators:

// test-utils.js
// ...
export * from '@testing-library/react'  // makes all React Testing Library's exports available
export * from '@render-with/decorators' // overrides React Testing Library's render function
export * from '@render-with/redux'      // makes decorators like withState(..) available

And finally, use the Redux decorators in your tests:

import { render, withState } from './test-utils'

it('shows loaded users', () => {
  render(<Users />, withState({ users: [ { name: 'John' } ] }))
  // ...
})

Test Scenarios

The following examples represent tests for this <Users /> component:

const Users = () => {
  const users = useSelector(state => state.users)
  const dispatch = useDispatch()
  
  useEffect(() => {
    dispatch(loadUsers())
  }, [])
  
  return (
    <div>
      <h1>Users</h1>
      {users ? (
        <ul>
          users.map(user => <li>user</li>)
        </ul>
      ) : (
        <div>Loading users...</div>
      )}
    </div>
  )
}

Just need a Redux store?

If your test does not care about the initial state, state changes, or dispatched actions, you can use the withStore(..) decorator and omit the store argument. The decorator will create and use a mock store for you:

import { render, screen, withStore } from './test-utils'

it('shows loading indicator initially', () => {
  render(<Users />, withStore())
  expect(screen.getByText(/loading/i)).toBeInTheDocument()
})

Need to verify dispatched actions?

If your test cares about dispatched actions, you can pass a mock store and inspect the recorded actions:

import { render, screen, withStore, configureMockStore } from './test-utils'

it('loads users', () => {
  const mockStore = configureMockStore()
  render(<Users />, withStore(mockStore))
  expect(mockStore.getActions()).toContainEqual(loadUsers())
})

Need to provide an initial state?

If your test cares about the initial state, you can use the withState(..) decorator:

import { render, screen, withState } from './test-utils'

it('loads users', () => {
  render(<Users />, withState({ users: [ { name: 'John' } ] }))
  expect(screen.getByRole('listitem')).toHaveTextContent('John')
})

Need to observe state changes or side effects?

If your test cares about state changes, you can pass an actual Redux store:

import { configureStore } from '@reduxjs/toolkit'
import { render, screen, withStore } from './test-utils'

it('shows loaded users', async () => {
  fetch.mockResponse([ { name: 'John' } ])
  const store = configureStore({ reducer: { users: usersReducer } })
  render(<Users />, withStore(store))
  expect(await screen.findByRole('listitem')).toHaveTextContent('John')
})

Mock store

If not specified otherwise, the decorators will create, configure, and use a mock store defined by redux-mock-store.

You can create, configure and pass your own mock store with createMockStore(..), which is re-exported by this library.

Mock thunk middleware

If not specified otherwise, the decorators will create a mock store using a mock-thunk middleware.

The mock thunk middleware will translate thunks into action objects with the { type: 'THUNK' } and mocked thunks into action objects with the { type: 'MOCKED_THUNK' }.

Replacing thunks with action objects is mainly done to avoid confusion when working with a mock store and thunks in tests.

Dispatched thunks will not be executed when using a mock store. Instead, they would end up in the recorded list of actions as follows:

console.log(mockStore.getActions()) // [ [Function] ]

This information is not very helpful and can be confusing when debugging a failing test. This is especially true when the thunks are defined as returning lambda functions (that have no name during runtime).

export const loadUsers = () => (dispatch, getState) => { /* ... */ }

And that's why the mock thunk middleware translates dispatched thunks into an action object with the type THUNK:

console.log(mockStore.getActions()) // [ { type: 'THUNK' } ]

The problem gets worse when the thunks are mocked and no return value (function) is configured:

console.log(mockStore.getActions()) // [ undefined ]

That's why the mock thunk middleware translates mocked thunks into an action object with the type MOCKED_THUNK:

console.log(mockStore.getActions()) // [ { type: 'MOCKED_THUNK' } ]

API

Note: This API reference uses simplified types. You can find the full type specification here.

function withState<S>(state?: State<S>): Decorator

Wraps component under test in a mock store initialized with the given state.

function withStore<S>(store?: Store<S>): Decorator

Wraps component under test in the given store.

function configureMockStore<S>(state?: S, middlewares?: Middleware[]): MockStore<S>

Returns a mock store initialized with the given state and configured to use the given middlewares.

const mockThunk: Middleware

A middleware that replaces thunks and mocked thunks with corresponding action objects.

Issues

Looking to contribute? PRs are welcome. Checkout this project's Issues on GitHub for existing issues.

🐛 Bugs

Please file an issue for bugs, missing documentation, or unexpected behavior.

See Bugs

💡 Feature Requests

Please file an issue to suggest new features. Vote on feature requests by adding a 👍. This helps maintainers prioritize what to work on.

See Feature Requests

📚 More Libraries

Please file an issue on the core project to suggest additional libraries that would benefit from decorators. Vote on library support adding a 👍. This helps maintainers prioritize what to work on.

See Library Requests

Questions

For questions related to using the library, file an issue on GitHub.

See Questions

Changelog

Every release is documented on the GitHub Releases page.

Contributors

Thanks goes to these people:

cultivate(software)
cultivate(software)

💼 💵
David Bieder
David Bieder

💻 ⚠️ 📖 👀 🚇 🚧 🤔
Jerome Weiß
Jerome Weiß

📖 🚇 🚧
Maurice Reichelt
Maurice Reichelt

📖 🚇 🚧

This project follows the all-contributors specification. Contributions of any kind welcome!

LICENSE

MIT

Package Sidebar

Install

npm i @render-with/redux

Weekly Downloads

1

Version

3.0.0

License

MIT

Unpacked Size

18.7 kB

Total Files

5

Last publish

Collaborators

  • mauricereichelt
  • davidbieder