React router - useOutletContext testing
Asked Answered
T

5

7

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

Thirteen answered 28/12, 2021 at 12:59 Comment(0)
N
11

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.

Ne answered 26/9, 2022 at 13:36 Comment(2)
This was really helpful also worked for me in storybook using react router. By wrapping the component in the <RenderRouteWithOutletContext>Fleet
what is the point of mocking outlet context data? what context data usually goes there?Hillman
F
2

I found usefull informations on this Stack post : https://mcmap.net/q/181827/-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(),
  })),
});
Fertility answered 18/10, 2022 at 15:14 Comment(1)
Can confirm that this answer works. My other answer to this question is still a viable way to achieve the same goal.Ne
R
1

You can mock the useOutletContext hook like this:

jest.mock("react-router-dom", () => ({
    ...jest.requireActual("react-router-dom"),
    useOutletContext: () => myContextData,
    })
);
Roseanneroseate answered 30/9, 2022 at 13:33 Comment(0)
H
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 />)
}
Hemeralopia answered 2/5, 2022 at 22:1 Comment(0)
D
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);
Dungeon answered 14/7, 2022 at 7:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.