How to mock React Navigation's navigation prop for unit tests with TypeScript in React Native?
Issue
The mock does not match the expected type so TypeScript reports an error.
Solution
You can use the type any
"to opt-out of type-checking and let the values pass through compile-time checks".
Details
As you mentioned, in JavaScript it works to mock only what is needed for the test.
In TypeScript the same mock will cause an error since it does not completely match the expected type.
In situations like these where you have a mock that you know does not match the expected type you can use any
to allow the mock to pass through compile-time checks.
Here is an updated test:
import { LoadingScreen } from "./LoadingScreen";import { shallow, ShallowWrapper } from "enzyme";import React from "react";import { View } from "react-native";const createTestProps = (props: Object) => ({ navigation: { navigate: jest.fn() }, ...props});describe("LoadingScreen", () => { describe("rendering", () => { let wrapper: ShallowWrapper; let props: any; // use type "any" to opt-out of type-checking beforeEach(() => { props = createTestProps({}); wrapper = shallow(<LoadingScreen {...props} />); // no compile-time error }); it("should render a <View />", () => { expect(wrapper.find(View)).toHaveLength(1); // SUCCESS expect(props.navigation.navigate).toHaveBeenCalledWith('LoginScreen'); // SUCCESS }); });});
I'm not super happy with my solution, but I've started mocking only the properties of Navigation that are needed for a specific component, for example:
export function UserHomeScreen({ navigation,}: { navigation: { goBack: () => void; navigate: NavigationFunction<UserStackParams, "AddPhoneNumber" | "ValidatePhoneNumber">; setOptions: (options: { title: string }) => void; };})
In the test, I can provide these as mocks:
const goBack = jest.fn();const navigate = jest.fn();const setOptions = jest.fn();renderer.create(<UserHomeScreen navigation={ { goBack, navigate, setOptions } } />)
The definition of NavigationFunction
is a mouthful as well:
export type NavigationFunction<ParamsList, Routes extends keyof ParamsList> = <T extends Routes>( target: Routes, params?: ParamsList[T]) => void;
I've not been able to come up with a correct signature for Navigator's functions addListener and removeListener
For Typescript users, an alternative approach just for interests sake.
For those that don't like using any, here's a solution that utilises Typescript's Partial utility type and Type assertions (using 'as')
import { NavigationScreenProp } from "react-navigation";import SomeComponent from "./SomeComponent";type NavigationScreenPropAlias = NavigationScreenProp<{}>;describe("Some test spec", () => { let navigation: Partial<NavigationScreenPropAlias>; beforeEach(() => { navigation = { dispatch: jest.fn() } }); test("Test 1", () => { const {} = render(<SomeComponent navigation={navigation as NavigationScreenPropAlias}/>); });});
Using Partial, we enjoy our editor/IDE intellisense features, the type assertion 'the 'as' used in the navigation prop definition could be replaced with an any, I just don't like using any if I don't have to.
You could build on the fundamentals in this code snippet to the specific scenario laid out in the question.