Ruby
Ruby

Reputation: 2949

Using "React Testing library" - How to wrap all my test component with providers

I'm using React-Testing-Library and I have my main.tsx file like this with all the Redux-Toolkit and React-Router wrappers as follows:

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <Provider store={store}>
        <Suspense fallback={<LoadingSpinner />}>
          <BrowserRouter>
            <Routes>
              <Route path="/*" element={<App />} />
            </Routes>
          </BrowserRouter>
        </Suspense>
    </Provider>
  </React.StrictMode>,
);

Now, when I try to test any component that uses any useDispatch or useNavigate the test fails unless I wrap that component with the providers like this:

test("Inputs should be initially empty", () => {
  render(
    <Provider store={store}>
      <BrowserRouter>
        <Login />
      </BrowserRouter>
      ,
    </Provider>,
  );
  const emailInput: HTMLInputElement = screen.getByRole("textbox", {
    name: /email/i,
  });
  expect(emailInput.value).toBe("");
});

Now, the test has passed and everything is good.

My question is, is there a way to initially wrap all the components I'm going to test by default with these wrappers? So that I don't have to wrap all my test components with these every time?

Upvotes: 7

Views: 5759

Answers (2)

josephnvu
josephnvu

Reputation: 1252

Here is an improved version of the https://testing-library.com/docs/react-testing-library/setup/#custom-render. The problem with the answer above (also found on the testing-library.com website), is that you can't use the "wrapper" property inside of the test-suite instance. This version allows you to use the "wrapper" property:

import { render, RenderOptions } from '@testing-library/react';
import { omit } from 'lodash';
import { ReactElement } from 'react';
import { ParentWrapper } from './parent-wrapper'

const customRender = (ui: ReactElement, renderOptions?: RenderOptions) => {
  return render(ui, {
    wrapper: props => {
      const renderedComponent = renderOptions?.wrapper ? <renderOptions.wrapper {...props} /> : props.children;
      return <ParentWrapper>{renderedComponent}</ParentWrapper>;
    },
    ...(renderOptions ? omit(renderOptions, 'wrapper') : {}),
  });
};

export * from '@testing-library/react';
export { customRender as render };

This way when you use the custom render you can add another wrapper:

import { Login, SubWrapper } from '.'

test("Inputs should be initially empty", () => {
  render(<Login />, {
    wrapper: props => <SubWrapper>{props.children}</SubWrapper>
  }); // <-- custom sub wrapper

  const emailInput: HTMLInputElement = screen.getByRole("textbox", {
    name: /email/i,
  });

  expect(emailInput.value).toBe("");
});

The output result should be following:

<ParentWrapper>
  <SubWrapper>
    <Login />
  </SubWrapper>
</ParentWrapper>

Upvotes: 1

Drew Reese
Drew Reese

Reputation: 203373

Create a wrapper component that renders any providers and wraps the children prop.

const ProviderWrapper = ({ children }) => (
  <Provider store={store}>
    <BrowserRouter>
      {children}
    </BrowserRouter>
  </Provider>
);
test("Inputs should be initially empty", () => {
  render(<Login />, { wrapper: ProviderWrapper });

  const emailInput: HTMLInputElement = screen.getByRole("textbox", {
    name: /email/i,
  });

  expect(emailInput.value).toBe("");
});

If you just need to wrap every component you test with the same providers then you can also create a custom render function.

Example:

import {render} from '@testing-library/react';

const ProviderWrapper = ({ children }) => (
  <Provider store={store}>
    <BrowserRouter>
      {children}
    </BrowserRouter>
  </Provider>
);

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

// re-export everything
export * from '@testing-library/react';

// override render method
export { customRender as render };
test("Inputs should be initially empty", () => {
  render(<Login />); // <-- custom render already wraps with providers

  const emailInput: HTMLInputElement = screen.getByRole("textbox", {
    name: /email/i,
  });

  expect(emailInput.value).toBe("");
});

Upvotes: 16

Related Questions