J. Hesters
J. Hesters

Reputation: 14768

React Navigation: How to test components that use the withNavigation HOC?

I'm writing unit tests with Jest for my React Native app. I have a component, which is wrapped in the withNavigation HOC.

My problem is that my tests crash, throwing:

● LoginScreen component › given no props: should render a login form

    Invariant Violation: withNavigation can only be used on a view hierarchy of a navigator. The wrapped component is unable to get access to navigation from props or context.

I'm using @testing-library/react-native for my tests and I set up a custom render method like this:

import { render } from '@testing-library/react-native';
import React from 'react';
import { NavigationContext } from 'react-navigation';
import { Provider } from 'react-redux';

import store from '../src/store';

const WithProviders = ({ children }) => {
  return (
    <Provider store={store}>
      <NavigationContext.Provider>{children}</NavigationContext.Provider>
    </Provider>
  );
};

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

export * from '@testing-library/react-native';

export { customRender as render };

This works for the Redux context, but doesn't work for the navigation provider.

How do you test a component that is wrapped in the withNavigation HOC?

Update:

I tried the suggested answer like this:

jest.mock('react-navigation', () => ({
  withNavigation: Component => props => <Component {...props} />,
}));

afterAll(() => {
  jest.restoreAllMocks();
});

But this doesn't work. I get the error:

Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Upvotes: 9

Views: 10078

Answers (2)

J. Hesters
J. Hesters

Reputation: 14768

Based on Kubilay's answer, here is how I solved it:

jest.mock('react-navigation', () => ({
  withNavigation: Component => props => (
    <Component navigation={{ navigate: jest.fn() }} {...props} />
  ),
  SafeAreaView: ({ children }) => <>{children}</>,
}));

afterAll(() => {
  jest.restoreAllMocks();
});

I had to mock withNavigation as well as SafeAreaView.

This is still a very dissatisfying way. If anyone has an idea how to inject the correct React Navigation provider in the custom render method I would be super thankful.

Ideally there should be a way to configure a custom renderer that wraps the content in a container. Here is how I do it with Redux.

import { render } from '@testing-library/react-native';
import React from 'react';
import { Provider } from 'react-redux';

import configureStore from '../src/redux/store.js';

const store = configureStore();

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

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

export * from '@testing-library/react-native';

export { customRender as render };

Upvotes: 10

Kubilay Kiymaci
Kubilay Kiymaci

Reputation: 472

You should mock react-navigation, like this:

jest.mock("react-navigation", ({ withNavigation: (component) => component })

And then pass props, to your component:

const mockProps = {
    navigation: { navigate: jest.fn() }
}

wrapper = shallow(<LoginScreen {...mockProps}/>)

Upvotes: 1

Related Questions