6

I'm using react-router V6 and trying to test the new feature of useOutletContext. my testing library is testing-library/react and I'm not sure how to pass the Context data in the test.

In the TSX component, I'm getting the data with the hook of react-router:

const { data } = useOutletContext<IContext>()

I need something like:

test("render outlet context data view", async () => {
  const { getByTestId } = render(
    <MockedProvider mocks={[mockData]} context={myContextData}>
       <ContextDataView />
    </MockedProvider>
)

the MockedProvider tag is from @apollo/client/testing

the context={myContextData} part is what i need

Ar26
  • 949
  • 1
  • 12
  • 29

5 Answers5

8

Instead of mocking useOutletContext I used composition and React Router's MemoryRouter to mimic the behaviour of the real app.

I created a RenderRouteWithOutletContext component that should be used to wrap the component you're testing.

// RenderRouteWithOutletContext.tsx
import { ReactNode } from 'react';
import { MemoryRouter, Outlet, Route, Routes } from 'react-router-dom';

interface RenderRouteWithOutletContextProps<T = any> {
  context: T;
  children: ReactNode;
}

export const RenderRouteWithOutletContext = <T,>({
  context,
  children,
}: RenderRouteWithOutletContextProps<T>) => {
  return (
    <MemoryRouter>
      <Routes>
        <Route path="/"element={<Outlet context={context as T} />}>
          <Route index element={children} />
        </Route>
      </Routes>
    </MemoryRouter>
  );
};

And in your test file:

// YourComponent.test.tsx
import { screen, cleanup, render } from '@testing-library/react';
import { describe, expect, it, afterEach } from 'vitest';
import { RenderRouteWithOutletContext } from './RenderRouteWithOutletContext';

const mockOutletContextData: any = {
  foo: 'bar',
};

afterEach(() => {
  cleanup();
});

describe('PersonOverview', () => {
  it('should render as expected', () => {
    render(
      <RenderRouteWithOutletContext context={mockOutletContextData}>
        <YourComponent />
      </RenderRouteWithOutletContext>,
    );
    const component = screen.getByTestId('component-test-id');
    expect(component).toBeInTheDocument();
  });
});

Notice I'm using Vitest above but the Jest version of this is almost exactly the same.

This solution is great because it is very similar to how your app is actually used.

Zander
  • 2,471
  • 3
  • 31
  • 53
2

I found usefull informations on this Stack post : https://stackoverflow.com/questions/58117890/how-to-test-components-using-new-react-router-hooks/58206121#58206121

The right way to mock useOutletContext is to use the mock function like this :

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useOutletContext: () => ({
    data: mockedData,
  }),
}));

The mockedData is an object of data I'm using for the test.

At this point I had a small error TypeError: window.matchMedia is not a function. I found a solution in an other stack post (the solution is mentioned in jest documentation)

Here is the code to add to your test :

Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: jest.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(), // deprecated
    removeListener: jest.fn(), // deprecated
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn(),
  })),
});
Caillou
  • 21
  • 2
  • Can confirm that this answer works. My other answer to this question is still a viable way to achieve the same goal. – Zander Jun 08 '23 at 16:28
1

You can mock the useOutletContext hook like this:

jest.mock("react-router-dom", () => ({
    ...jest.requireActual("react-router-dom"),
    useOutletContext: () => myContextData,
    })
);
CHues
  • 151
  • 1
  • 6
0

I needed the same thing at work, and one of my colleagues helped me finally figure it out.

in your test file

import * as rrd from 'react-router-dom';

then set up your data just like you'd expect, and use Jest to mock React-router-dom

let mockData = { mock: 'Data' }
jest.mock('react-router-dom');

and then in your test

test("render outlet context data view", async () => {
  rrd.useOutletContext.mockReturnValue(mockData)
  render(<ContextDataView />)
}
Mohkan
  • 1
  • 1
0

I performed the following to mock one of the objects on my Outlet Context:

Outlet defined in Layout.tsx:

<Outlet context={{myModel1, myModel2, myModel3}} />

Test class:

import { act, cleanup, render, screen } from '@testing-library/react';
import { IMyModel1 } from 'Models/IMyModel1';
import * as rrd from 'react-router-dom';

jest.mock('react-router-dom');
const mockedOutletContext = rrd as jest.Mocked<typeof rrd>;

afterEach(() => {
    cleanup;
    mockedOutletContext.Outlet.mockReset();
  });

Within your test, mock the object as required:

const myModel1: IMyModel1 = {};
const outletContext: rrd.OutletProps = { context: { myModel1: myModel1 } };
mockedOutletContext.useOutletContext.mockReturnValue(outletContext.context);