A common question when testing React components is what to do with React components that use context values. If you take a step back and consider the guiding testing philosophy of writing tests that resemble the way our software is used, then you’ll know that you want to render your component with the provider:
render(<ContextProvider><ComponentToTest /></ContextProvider>,)
The one problem with this is if you want to re-render the <ComponentToTest />
(for example, to give it new props and test how it responds to updated props),
then you have to include the context providers:
const {rerender} = render(<ContextProvider><ComponentToTest /></ContextProvider>,)rerender(<ContextProvider><ComponentToTest newProp={true} /></ContextProvider>,)
This is kind of annoying, so instead, you can provide a wrapper
option and
that will ensure that rerenders are wrapped as well:
function Wrapper({children}) {return <ContextProvider>{children}</ContextProvider>}const {rerender} = render(<ComponentToTest />, {wrapper: Wrapper})rerender(<ComponentToTest newProp={true} />)
📜 https://testing-library.com/docs/react-testing-library/api#wrapper
This Wrapper
could include providers for all your context providers in your
app: Router, Theme, Authentication, etc.
To take it further, you could create your own custom render method that does this automatically:
import {render as rtlRender} from '@testing-library/react'// "rtl" is short for "react testing library" not "right-to-left" 😅function render(ui, options) {return rtlRender(ui, {wrapper: Wrapper, ...options})}// then in your tests, you don't need to worry about context at all:const {rerender} = render(<ComponentToTest />)rerender(<ComponentToTest newProp={true} />)
From there, you can put that custom render function in your own module and use your custom render method instead of the built-in one from React Testing Library. Learn more about this from the docs:
📜 https://testing-library.com/docs/react-testing-library/setup
In this exercise, we have an “Easy Button” that’s styled differently based on
the Theme context. Your job is to assert on the styles it has, but you first
need to render the UI with the ThemeProvider (and set the initialTheme
value).
Should mostly be a copy/paste and change the initialTheme
and assertion a bit.
The duplication is cramping my style. Create a custom render method that
encapsulates this shared logic. It’ll need to accept an option for the theme
(dark or light).
We’ve actually already created a custom render method for this! So swap your
import
of @testing-library/react
with test/test-utils
which you can find
in ./src/test/test-utils.js
.
After the instruction, if you want to remember what you’ve just learned, then fill out the elaboration and feedback form:
import * as React from 'react'import {render as rtlRender} from '@testing-library/react'import {ThemeProvider} from 'components/theme'function render(ui, {theme = 'light', ...options} = {}) {const Wrapper = ({children}) => (<ThemeProvider initialTheme={theme}>{children}</ThemeProvider>)return rtlRender(ui, {wrapper: Wrapper, ...options})}export * from '@testing-library/react'// override React Testing Library's render with our ownexport {render}
import * as React from 'react'import {render, screen} from 'test/test-utils'import EasyButton from '../../components/easy-button'test('renders with the light styles for the light theme', () => {render(<EasyButton>Easy</EasyButton>, {theme: 'light'})const button = screen.getByRole('button', {name: /easy/i})expect(button).toHaveStyle(`background-color: white;color: black;`)})test('renders with the dark styles for the dark theme', () => {render(<EasyButton>Easy</EasyButton>, {theme: 'dark'})const button = screen.getByRole('button', {name: /easy/i})expect(button).toHaveStyle(`background-color: black;color: white;`)})
Quick Links
Legal Stuff
Social Media