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.