npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@render-with/decorators

v5.0.0

Published

A render function that enables the use of decorators that elegantly wrap a component under test in providers.

Downloads

2,207

Readme

Render decorators 🪆 for React Testing Library

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

A render function that enables the use of decorators which elegantly wrap a component under test in providers:

render(<LoginForm />, withStore({ user: 'john.doe' }), withLocation('/login'), withTheme())

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:

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

or

for installation via yarn:

yarn add --dev @render-with/decorators

This library has the following peerDependencies:

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:

// 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

Then, install some decorators for the libraries used in your project:

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

Note: You can find an (incomplete) list of libraries with render decorators here.

Next, configure the decorators in your test-utils file (please refer to the individual decorator library's documentation for a complete setup guide):

// test-utils.js
// ...
export * from '@render-with/react-router' // makes decorators like withLocation(..) available
export * from '@render-with/redux'        // makes decorators like withState(..) available

And finally, use the decorators in your tests:

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

it('shows home page when logged in already', async () => {
  render(<LoginForm />, withStore({ user: 'john.doe' }), withLocation('/login'), withTheme())
  // ...
})

Note: With configureRender it is possible to create a custom render function with default decorators that will be applied in all tests without having to explicitly mention the default decorators.

The problem

Rendering React components in tests often requires wrapping them in all kinds of providers. Some apps require dozens of providers to function correctly. For instance:

  • React Router MemoryRouter
  • Redux StoreProvider
  • Material UI ThemeProvider
  • Format.JS IntlProvider
  • Backstage ApiProvider
  • ...

And there are, of course, all the custom Context.Providers.

React Testing Library recommends creating a custom render function:

// test-utils.js
const AllTheProviders = ({ children }) => (
  <ProviderA {/* ... */}>
    <ProviderB {/* ... */}>
      <!-- ... -->
        <ProviderZ {/* ... */}>
          {children}
        </ProviderZ>
      <!-- ... -->
    </ProviderB>
  </ProviderA>
)

const customRender = (ui, options) =>
  render(ui, {wrapper: AllTheProviders, ...options})

export { customRender as render }

But a custom render function is not always the best solution.

Some larger projects require a lot of providers and rendering all providers is not always possible.

Some tests need a little more control over the providers being rendered.

Defining a custom render function on a test-file-basis is possible, but it can introduce a lot of boilerplate code:

import { configureStore } from '@reduxjs/toolkit'

const renderComponent = () => render(
  <ThemeProvier theme='light'>
    <MemoryRouter initialEntries={[ '/login' ]} initialIndex={0}>
      <StoreProvider store={configureStore({ reducer, preloadedState: { user: 'john.doe' }, middleware })}>
        <LoginForm />
      </StoreProvider>
    </MemoryRouter> 
  </ThemeProvier>
)

// ...

it('shows home page when logged in already', async () => {
  renderComponent()
  await userEvent.click(screen.getByRole('button', { name: /login/i }))
  expect(screen.getByRole('heading', { name: /home/i })).toBeInTheDocument()
})

Another downside of the renderComponent() test helper function above is: The test becomes harder to read.

It is unclear what component is actually rendered by just looking at the test itself. What component is the test testing?

The solution

This library provides a customized render function that accepts the component under test and a set of elegant decorators that can be used to wrap a rendered component:

it('shows home page when logged in already', async () => {
  render(<LoginForm />, withStore({ user: 'john.doe' }), withLocation('/login'), withTheme())
  await userEvent.click(screen.getByRole('button', { name: /login/i }))
  expect(screen.getByRole('heading', { name: /home/i })).toBeInTheDocument()
})

Here are some benefits of using render decorators:

  • more maintainable code (one line vs. many lines of boilerplate code; easier to read)
  • autocompletion (just type with and pick a decorator)
  • flexibility (pick only the providers needed for the test case at hand)
  • less mocking (decorators mock what needs to be mocked)
  • simplicity (some providers make non-trivial testing aspects, like navigation, easy to test)
  • customization (decorators can often be configured further with additional arguments)
  • improved performance (no need to render providers that are not needed)

Note: This solution is partly inspired by Storybook Decorators.

Wrapping Order

The order of the decorators determines how the rendered component will be wrapped with providers.

The decorator listed first (closest to the component) will be first to wrap a provider around the component. The decorator listed last will be responsible for the outermost provider.

For instance:

render(<LoginForm />, withStore({ user: 'john.doe' }), withLocation('/login'), withTheme())

will result in:

<ThemeProvier theme='light'>
  <MemoryRouter initialEntries={[ '/login' ]} initialIndex={0}>
    <StoreProvider store={configureStore({ reducer, preloadedState: { user: 'john.doe' }, middleware })}>
      <LoginForm />
    </StoreProvider>
  </MemoryRouter>
</ThemeProvier>

Note: It works a bit like Matrjoschka 🪆 puppets.

Decorators

Here's an (incomplete) list of libraries that provide decorators for some known libraries:

API

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

function render(ui: ReactElement, ...decorators: Decorator[]): RenderResult

Wraps the element (usually the component under test) in providers using the given list of decorators and renders the final result. Providing no decorators will simply render the element.

function configureRender(...defaultDecorators: Decorator[]): typeof render

Creates a render function that wraps the component under test in providers using the given list of default decorators and (if applicable) in providers using the list of decorators passed to the created render itself.

function decorate(ui: ReactElement, ...decorators: Decorator[]): ReactElement

Wraps the given element in providers using the given list of decorators. Providing no decorators will simply return the element.

type Decorator = (ui: ReactElement) => ReactElement;

A Decorator wraps an element in a provider and returns the resulting element.

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:

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

LICENSE

MIT