Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
May 26, 2022 08:35 pm GMT

Smoothly test React components with multiple Contexts.

Credits: Photo by Tima Miroshnichenko

Yes yes I know, testing, I'll be honest I hate (start) writing tests, but, once I start, I love it, the problem is that then I want to keep writing tests, and not coding LOL, just joking but is kinda like that, you may suffer from the same mix of feelings

This is a very very interesting toping since many developers even seniors sometimes don't know where to start (starting is the issue as you can see), or how we can use utils or helpers to reduce the boilerplate in our components especially when I want to test components wrapped in several Context Providers, do I need to repeat my self on every test file? Hopefully, this will make your life easier from now on, let's get into it!... We will be using react testing library, of course.

The problem

We have an application that has some Context, and our components consume those Context values, now we need to test these components, and we want to definitely pass customs values to our components Providers to try to assert the results in our unit tests

The initial solution

Initially, you may think let's export our Provider and pass the custom values etc and expect some results, well yes and no, this is a problem for the next reasons

  • Repeat your self all the time in all files but adding the Context Provider with the values
  • If you need to render the component that you need you want to test with more than one Context this may become hard to read and very boilerplate

Let's take a simple Context example

const initialState = {  name: "alex",  age: 39};const MyContext = React.createContext(initialState);export const useMyContext = () => React.useContext(MyContext);const reducer = (currentState, newState) => ({ ...currentState, ...newState });export const MyContextProvider = ({ children }) => {  const [state, setState] = React.useReducer(reducer, initialState);  return (    <MyContext.Provider value={{ state, setState }}>      {children}    </MyContext.Provider>  );};

BTW you can make this cooler but destructuring the Provider from the Context all in one line bla bla, notice the cool useReducer :), but is the same basically, so, you will use this Context like:

export default function App() {  return (    <MyContextProvider>      <Component />    </MyContextProvider>  );}

And in component you can use you Context by using the custom hook you already declared in the Context file, something like:

function Component() {  const { state, setState } = useMyContext();  return (    <div>      <input        value={state.name}        onChange={(e) => setState({ name: e.target.value })}      />      Name: {state.name}, Last Name: {state.lastName}    </div>  );}

Now you want to test this component right?, What do you do? Export the Context to declare again the wrapper in my test and passing custom values, let go to our Context file and export our context

export const MyContext = React.createContext(initialState);

Now in your test you will do something like

import { render } from '@testing-library/react';const renderComponent() {  return (    render(      <MyContext.Provider value={{ mockState, mockFnc}}>        <Component>      </MyContext.Provider>    )  )}// ... test

This is fine if your component uses only one Context, but if you use several? And even if is one, you need to do these in all your tests

Solution: the custom render

Let's build a custom render method that returns our component wrapped in as many Contexts we want with as many Provider values we want!

// /testUtils/index.js// custom renderimport { render as rtlRender } from '@testing-library/react';// our custom renderexport const render = (ui, renderOptions) => {    try {        return rtlRender(setupComponent(ui, renderOptions));    } catch (error: unknown) {        throw new Error('Render rest util error');    }};

This utility method will expect to params, the component, called ui, and the options, and it will use setupComponent method to render the view as a normal react component, let's finished!

// /testUtils/index.js// import all the Context you will use in the appimport {MyContext} from './MyContext'import {MyContext1} from './MyContext'import {MyContext2} from './MyContext'const CONTEXT_MAP = {  MyContext,  MyContext1,  MyContext2}const setupComponent = (ui, renderOptions) => {  const { withContext } = renderOptions;  if (withContext == null) return ui;  return (      <>          {withContext.reduceRight((acc, { context, contextValue }) => {              const Ctx = CONTEXT_MAP[context];              return <Ctx.Provider value={contextValue}>{acc}</Ctx.Provider>;          }, ui)}      </>  );};

By reducing right you ensure the first Context you pass, will the at the first to be rendered, nice eh? Final file looks like:

// /testUtils/index.js// import all the context you will use in the appimport { render as rtlRender } from '@testing-library/react';import {MyContext} from './MyContext'import {MyContext1} from './MyContext'import {MyContext2} from './MyContext'const CONTEXT_MAP = {  MyContext,  MyContext1,  MyContext2}const setupComponent = (ui, renderOptions) => {  const { withContext } = renderOptions;  if (withContext == null) return ui;  return (      <>          {withContext.reduceRight((acc, { context, contextValue }) => {              const Ctx = CONTEXT_MAP[context];              return <Ctx.Provider value={contextValue}>{acc}</Ctx.Provider>;          }, ui)}      </>  );};// our custom renderexport const render = (ui, renderOptions) => {    try {        return rtlRender(setupComponent(ui, renderOptions));    } catch (error: unknown) {        throw new Error('Render rest util error');    }};

Now the same test will look like this:

import { render } from './testUtils';const renderComponent() {  return (    render(        <Component/>,        [{context: "MyContext", contextValue: {name: 'Max', lastName: "Smith"}}]    )  )}// test ...

The cool thing is that in the array of Contexts you can pass as many of them as you want, following the format of {context, contextValue}, of course, the recommendation is to use typescript, but it will make the article longer, but now you got the idea if you have any issues turning this into TS let me know I can help. That's it guys, let me know if you use any other trick or if you do it using a different approach. Happy coding!


Original Link: https://dev.to/alexandprivate/smoothly-test-react-components-with-multiple-contexts-453f

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To