I'm building a React Native app with TypeScript. For my navigation I use React Navigation and for my unit testing I use Jest and Enzyme.
Here is the (stripped down) code for one of my screen (LoadingScreen.tsx):
import styles from "./styles";import React, { Component } from "react";import { Text, View } from "react-native";import { NavigationScreenProps } from "react-navigation";// Is this correct?export class LoadingScreen extends Component<NavigationScreenProps> {// Or should I've done:// export interface Props {// navigation: NavigationScreenProp<any, any>;// }// export class LoadingScreen extends Component<Props> { componentDidMount = () => { this.props.navigation.navigate("LoginScreen"); }; render() { return (<View style={styles.container}><Text>This is the LoadingScreen.</Text></View> ); }}export default LoadingScreen;
When trying to test the screens I came across a problem. The screens expects a prop with a type of NavigiationScreenProps because I'm accessing React Navigations navigation
prop. Here is the testing file's code (LoadingScreen.test.tsx):
import { LoadingScreen } from "./LoadingScreen";import { shallow, ShallowWrapper } from "enzyme";import React from "react";import { View } from "react-native";import * as navigation from "react-navigation";const createTestProps = (props: Object) => ({ ...props});describe("LoadingScreen", () => { describe("rendering", () => { let wrapper: ShallowWrapper; let props: Object; beforeEach(() => { props = createTestProps({}); wrapper = shallow(<LoadingScreen {...props} />); }); it("should render a <View />", () => { expect(wrapper.find(View)).toHaveLength(1); }); });});
The problem is, that LoadingScreen
expects a navigation
prop.
I get the error:
[ts]Type '{ constructor: Function; toString(): string; toLocaleString(): string; valueOf(): Object; hasOwnProperty(v: string | number | symbol): boolean; isPrototypeOf(v: Object): boolean; propertyIsEnumerable(v: string | ... 1 more ... | symbol): boolean; }' is not assignable to type 'Readonly<NavigationScreenProps<NavigationParams, any>>'. Property 'navigation' is missing in type '{ constructor: Function; toString(): string; toLocaleString(): string; valueOf(): Object; hasOwnProperty(v: string | number | symbol): boolean; isPrototypeOf(v: Object): boolean; propertyIsEnumerable(v: string | ... 1 more ... | symbol): boolean; }'.(alias) class LoadingScreen
How can I fix this?
I think I somehow have to mock the navigation
prop. I tried doing that (as you can see I imported *
from React Navigation in my test), but couldn't figure out. There is only NavigationActions
that is remotely useful but it only includes navigate()
. TypeScript expects everything, even the state, to be mocked. How can I mock the navigation
prop?
Edit 1: Is the approach of using NavigationScreenProps
even correct or should I use the interface Props
approach? If yes how would you then mock than (it results in the same error).
Edit 2:Using the second approach with the interface and
export class LoadingScreen extends Component<Props, object>
I was able to "solve" this problem. I literally had to mock every single property of the navigation object like this:
const createTestProps = (props: Object) => ({ navigation: { state: { params: {} }, dispatch: jest.fn(), goBack: jest.fn(), dismiss: jest.fn(), navigate: jest.fn(), openDrawer: jest.fn(), closeDrawer: jest.fn(), toggleDrawer: jest.fn(), getParam: jest.fn(), setParams: jest.fn(), addListener: jest.fn(), push: jest.fn(), replace: jest.fn(), pop: jest.fn(), popToTop: jest.fn(), isFocused: jest.fn() }, ...props});
The question remains: Is this correct? Or is there a better solution?
Edit 3:Back when I used JS, it was enough to mock only the property I needed (often just navigate). But since I started using TypeScript, I had to mock every single aspects of navigation. Otherwise TypeScript would complain that the component expects a prop with a different type.