React router - useOutletContext testing - reactjs

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

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.

You can mock the useOutletContext hook like this:
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useOutletContext: () => myContextData,
})
);

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 />)
}

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);

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(),
})),
});

Related

[TypeError: (0 , react_router_dom_1.useParams) is not a function]

I have a component which uses react-router-dom's useParams hook to get params.
It is used in my component as :
/// Child.tsx
const Child = () => {
const {id} = useParams();
return <div>Child!</div>
}
while writing unit tests, i'm facing the above error while mounting the component.
Things I tried is mocking useParams like this :
jest.mock('react-router-dom', () => ({ /// This is at top level after imports
...jest.requireActual('react-router-dom'),
useParams: () => ({
id: 'txnabcd',
}),
}))
Unit test:
descibe("mounts", ()=>{
it("mounts the component", ()=>{
mount(<Child /> /// Failing here
})
})
Where could I be possibly wrong?
Instead of mocking 3rd-party code you might try rendering the Child component into a routing context and route with an id route param.
Example:
import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
describe("mounts", () => {
it("mounts the component", () => {
mount(
<Router initialEntries={["/test/1234"]}>
<Routes>
<Route path="/test/:id" element={<Child />} />
</Routes>
</Router>
);
});
});
I ended up mocking react-router-dom's useParams
// __ mocks __/react-router-dom.js
const reactRouter = require('react-router-dom')
const useParams = jest.fn()
module.exports = {
...reactRouter,
useParams,
}
Inside test-file standard import and mockReturnvalue for useParams:
import { useParams } from 'react-router-dom'
...
useParams.mockReturnValue({ id: 'txnabcd' })

Testing pages secured by react-keycloak

In my React app, I am using keycloak and the KeycloakProvider from the "react-keycloak/web" library.
Certain routes are protected using a PrivateRoute component that pulls the keycloak state using the useKeycloak() hook and checking the user is authorised and has the correct roles:
const [keycloak, initialized] = useKeycloak();
My issue comes with testing my PrivateRoute component. No matter what I try initialized always remains false, blocking the page from rendering.
Using the useKeycloak.test.js file as inspiration, I made a MockAuthProvider component to wrap around my tests:
import React from "react";
import { KeycloakProvider } from "#react-keycloak/web";
export const createKeycloakStub = () => ({
init: jest.fn().mockResolvedValue(true),
updateToken: jest.fn(),
login: jest.fn(),
logout: jest.fn(),
register: jest.fn(),
accountManagement: jest.fn(),
createLoginUrl: jest.fn(),
...
});
interface MockedAuthProviderProps {
children: React.ReactChild;
mocks: { [key: string]: typeof jest.fn };
}
export const MockedAuthProvider = (props: MockedAuthProviderProps) => {
const { children, mocks } = props;
const defaultMocks = createKeycloakStub();
const keycloak = { ...defaultMocks, ...mocks };
return (
<KeycloakProvider keycloak={keycloak}>
{children}
</KeycloakProvider>
);
};
I then use it in my tests as so
<MockedAuthProvider mocks={keycloakMocks}>
<MemoryRouter initialEntries={["/secret"]}>
<PrivateRoute roles={roles} path="/secret">
<div>Protected Content</div>
</PrivateRoute>
</MemoryRouter>
</MockedAuthProvider>
Has anyone found a way of successfully testing components in conjunction with useKeycloak or getting initialized to be true?
Thanks
I managed to work around it by mocking the hook itself. It's not quite as clean as I would have liked, but works well enough for now.
let mockInitialized = false;
jest.mock("#react-keycloak/web", () => {
const originalModule = jest.requireActual("#react-keycloak/web");
return {
...originalModule,
useKeycloak: () => [
mockKeycloakStub,
mockInitialized
]
};
})
I can then set the value of mockInitialized in each test, depending on what I want the initialized status to be.

Problems testing a Redux + React app with enzyme:

I have this component
import React, { useEffect } from 'react';
import './App.css';
import { connect } from 'react-redux';
import { CircularProgress } from '#material-ui/core';
import { loadPhones } from './redux/actions/actions.js';
import TablePhones from './Table.js';
const mapStateToProps = (state) => state;
function mapDispatchToProps(dispatch) {
return {
loadPhones: () => {
dispatch(loadPhones());
},
};
}
export function App(props) {
useEffect(() => {
props.loadPhones();
}, []);
if (props.phones.data) {
return (
<div className="App">
<div className="introductoryNav">Phones</div>
<TablePhones phones={props.phones.data} />
</div>
);
}
return (
<div className="gridLoadingContainer">
<CircularProgress color="secondary" iconStyle="width: 1000, height:1000" />
<p className="loadingText1">Loading...</p>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
For whom ive written
import React from 'react';
import { render } from '#testing-library/react';
import { Provider } from "react-redux";
import App from './App';
import { shallow, mount } from "enzyme";
import configureMockStore from "redux-mock-store";
const mockStore = configureMockStore();
const store = mockStore({});
describe('App comp testing', () => {
it("should render without throwing an error", () => {
const app = mount(
<Provider store={store}>
<App />
</Provider>
).dive()
expect(app.find('.introductoryNav').text()).toContain("Phones");
});
})
But that test keeps failing
ypeError: Cannot read property 'data' of undefined
I also tried importing App as {App} instead and using shallow testing, but no luck. It gives the same erros, so im left without access to the context, and I cant keep doing my tests
How can I solve this?
You could use the non-default export of your component here and shallow render test if you pass your component the props and don't try to mock the store (if I recall correctly).
I was thinking something like this might work, tesing the "pure" non-store connected version of the component. This seems to be a popular answer for this question as this was asked (in a different way) before here:
import React from 'react';
import { App } from './App';
import { shallow } from "enzyme";
// useful function that is reusable for desturcturing the returned
// wrapper and object of props inside of beforeAll etc...
const setupFunc = overrideProps => {
const props = {
phones: {
...phones, // replace with a mock example of a render of props.phones
data: {
...phoneData // replace with a mock example of a render of props.phones.data
},
},
loadPhones: jest.fn()
};
const wrapper = shallow(<App {...props} />);
return {
wrapper,
props
};
};
// this is just the way I personally write my inital describe, I find it the easiest way
// to describe the component being rendered. (alot of the things below will be opinios on test improvements as well).
describe('<App />', () => {
describe('When the component has intially rendered' () => {
beforeAll(() => {
const { props } = setupFunc();
});
it ('should call loadPhones after the component has initially rendered, () => {
expect(props.loadPhones).toHaveBeenCalled();
});
});
describe('When it renders WITH props present', () => {
// we should use describes to section our tests as per how the code is written
// 1. when it renders with the props present in the component
// 2. when it renders without the props
beforeAll(() => {
const { wrapper, props } = setupFunc();
});
// "render without throwing an error" sounds quite broad or more like
// how you would "describe" how it rendered before testing something
// inside of the render. We want to have our "it" represent what we're
// actually testing; that introductoryNave has rendered with text.
it("should render an introductoryNav with text", () => {
// toContain is a bit broad, toBe would be more specific
expect(wrapper.find('.introductoryNav').text()).toBe("Phones");
});
it("should render a TablePhones component with data from props", () => {
// iirc toEqual should work here, you might need toStrictEqual though.
expect(wrapper.find('TablePhones').prop('phones')).toEqual(props.phones);
});
});
describe('When it renders WITHOUT props present', () => {
it("should render with some loading components", () => {
expect(wrapper.find('.gridLoadingContainer').exists()).toBeTruthy();
expect(wrapper.find('CircularProgress').exists()).toBeTruthy();
expect(wrapper.find('.loadingText1').exists()).toBeTruthy();
});
});
});

Shallow copy a React component with a provided store in Enzyme

When I try shallow(<LoginForm />) I get the following error Invariant Violation: Could not find react-redux context value; please ensure the component is wrapped in a <Provider>. So in order to fix this, I tried:
const wrapper = shallow(
<Provider store={store}>
<LoginForm />
</Provider>
);
This works, however, the debug output is:
<ContextProvider value={{...}}>
<LoginForm />
</ContextProvider>
But I also want to render the LoginForm. Some other things I tried to get this fixed:
wrapper.find(LoginForm).shallow();
shallow(
<Provider store={store}>
<LoginForm />
</Provider>
).dive();
wrapper.find(LoginForm).shallow();
shallow(<LoginForm />, {
wrappingComponent: Provider,
wrappingComponentProps: { store }
});
But all of these result in the same error mentioned above. How am I able to fix this, while using the shallow method? Also, the LoginForm uses react hooks, including a useSelect hook, so passing a store to my component prop is not the solution I'm looking for.
You can mock useSelector, also handy mock selector function
import React from 'react';
import { mount, shallow } from 'enzyme';
import { getIsAuthorized } from 'modules/auth/reducer';
import SignIn from '../SignIn';
jest.mock('modules/auth/reducer');
jest.mock('react-redux', () => {
const RealModule = jest.requireActual('react-redux');
return {
...RealModule,
useSelector: (fn) => fn(),
};
});
interface SetupProp {
isAuthorized: boolean;
}
describe('Page: SignIn', () => {
const setupWrapper = ({ isAuthorized }: SetupProp) => {
(getIsAuthorized as jest.Mock).mockReturnValue(isAuthorized);
return shallow(<SignIn />);
};
test('should render form', () => {
const wrapper = setupWrapper({ isAuthorized: false });
expect(wrapper).toMatchSnapshot();
});
});
SignIn Component:
const SignIn: FunctionComponent = () => {
//...
const isAuthorized = useSelector(getIsAuthorized);
//...
}

Testing custom hook: Invariant Violation: could not find react-redux context value; please ensure the component is wrapped in a <Provider>

I got a custom hook which I want to test. It receives a redux store dispatch function and returns a function. In order to get the result I'm trying to do:
const { result } = renderHook(() => { useSaveAuthenticationDataToStorages(useDispatch())});
However, I get an error:
Invariant Violation: could not find react-redux context value; please ensure the component is wrapped in a
It happens because of the useDispatch and that there is no store connected. However, I don't have any component here to wrap with a provider.. I just need to test the hook which is simply saving data to a store.
How can I fix it?
The react hooks testing library docs go more into depth on this. However, what we essentially are missing is the provider which we can obtain by creating a wrapper. First we declare a component which will be our provider:
import { Provider } from 'react-redux'
const ReduxProvider = ({ children, reduxStore }) => (
<Provider store={reduxStore}>{children}</Provider>
)
then in our test we call
test("...", () => {
const store = configureStore();
const wrapper = ({ children }) => (
<ReduxProvider reduxStore={store}>{children}</ReduxProvider>
);
const { result } = renderHook(() => {
useSaveAuthenticationDataToStorages(useDispatch());
}, { wrapper });
// ... Rest of the logic
});
This is probably a late answer but you can also use this in your test
jest.mock('react-redux', () => {
const ActualReactRedux = jest.requireActual('react-redux');
return {
...ActualReactRedux,
useSelector: jest.fn().mockImplementation(() => {
return mockState;
}),
};
});
This issues is related your test file. You have to declarer provider and store in your test file.
Update or replace your app.test.tsx by below code
NB: Don't forget to install redux-mock-store if you don't have already.
import React from 'react';
import { render } from '#testing-library/react';
import App from './App';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
describe('With React Testing Library', () => {
const initialState = { output: 10 };
const mockStore = configureStore();
let store;
it('Shows "Hello world!"', () => {
store = mockStore(initialState);
const { getByText } = render(
<Provider store={store}>
<App />
</Provider>
);
expect(getByText('Hello World!')).not.toBeNull();
});
});
I got this solution after searching 1 hours.
Thanks a lot to OSTE
Original Solution: Github issues/8145 and solution link
With this solution if you get error like TypeError: window.matchMedia is not a function then solve by this way. add those line to your setupTests.ts file. Original solution link stackoverflow.com/a/64872224/5404861
global.matchMedia = global.matchMedia || function () {
return {
addListener: jest.fn(),
removeListener: jest.fn(),
};
};
I think you can create test-utils.[j|t]s(?x), or whatever you set the name of the file to, like this:
https://github.com/hafidzamr/nextjs-ts-redux-toolkit-quickstart/blob/main/__tests__/test-utils.tsx
//root(or wherever your the file)/test-utils.tsx
import React from 'react';
import { render, RenderOptions } from '#testing-library/react';
import { Provider } from 'react-redux';
// Import your store
import { store } from '#/store';
const Wrapper: React.FC = ({ children }) => <Provider store={store}>{children}</Provider>;
const customRender = (ui: React.ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => render(ui, { wrapper: Wrapper, ...options });
// re-export everything
export * from '#testing-library/react';
// override render method
export { customRender as render };
Use it like this:
https://github.com/hafidzamr/nextjs-ts-redux-toolkit-quickstart/blob/main/__tests__/pages/index.test.tsx
//__tests__/pages/index.test.tsx
import React from 'react';
import { render, screen } from '../test-utils';
import Home from '#/pages/index';
describe('Home Pages', () => {
test('Should be render', () => {
render(<Home />);
const getAText = screen.getByTestId('welcome');
expect(getAText).toBeInTheDocument();
});
});
Works for me.
screenshot work
BTW, if you place the test-utils.[j|t]s(?x) or whatever you set the name file place on the directory __test__, don't forget to ignore it on jest.config.js.
//jest.config.js
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/', '<rootDir>/__tests__/test-utils.tsx'],
repo: https://github.com/hafidzamr/nextjs-ts-redux-toolkit-quickstart

Resources