web-dev-qa-db-fra.com

Comment se moquer React Navigation prop de navigation pour les tests unitaires avec TypeScript en React Native?

Je crée une React application native avec TypeScript. Pour ma navigation, j'utilise React Navigation et pour mes tests unitaires, j'utilise Jest et Enzyme.

Voici le code (dépouillé) pour l'un de mes écrans (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;

En essayant de tester les écrans, j'ai rencontré un problème. Les écrans attendent un accessoire avec un type de NavigiationScreenProps car j'accède à React Navigations navigation prop. Voici le code du fichier de test (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);
    });
  });
});

Le problème est que LoadingScreen attend un accessoire navigation.

Je reçois l'erreur:

[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

Comment puis-je réparer cela?

Je pense que je dois en quelque sorte se moquer de l'hélice navigation. J'ai essayé de faire cela (comme vous pouvez le voir, j'ai importé * De React Navigation dans mon test), mais je n'ai pas pu le comprendre. Il n'y a que NavigationActions qui est utile à distance mais qui ne comprend que navigate(). TypeScript s'attend à ce que tout, même l'état, soit moqué. Comment puis-je me moquer de l'hélice navigation?

Edit 1: L'approche de l'utilisation de NavigationScreenProps est-elle même correcte ou dois-je utiliser l'approche interface Props? Si oui, comment vous moqueriez-vous alors (cela entraîne la même erreur).

Edit 2: En utilisant la seconde approche avec l'interface et

export class LoadingScreen extends Component<Props, object>

J'ai pu "résoudre" ce problème. J'ai dû littéralement me moquer de chaque propriété de l'objet de navigation comme ceci:

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

La question demeure: est-ce correct? Ou bien y a-t-il une meilleure solution?

Edit 3: Retour quand j'ai utilisé JS, il suffisait de se moquer uniquement de la propriété dont j'avais besoin (souvent juste naviguer). Mais depuis que j'ai commencé à utiliser TypeScript, j'ai dû me moquer de tous les aspects de la navigation. Sinon, TypeScript se plaindrait que le composant attend un accessoire avec un type différent.

12
J. Hesters

Problème

La maquette ne correspond pas au type attendu, donc TypeScript signale une erreur.

Solution

Vous pouvez utiliser le type any"pour désactiver la vérification de type et laisser les valeurs passer par les vérifications au moment de la compilation" .

Détails

Comme vous l'avez mentionné, en JavaScript, cela ne fonctionne que pour se moquer de ce qui est nécessaire pour le test.

Dans TypeScript, le même faux provoquera une erreur car il ne correspond pas complètement au type attendu.

Dans des situations comme celles-ci où vous avez une maquette dont vous savez qu'elle ne correspond pas au type attendu, vous pouvez utiliser any pour permettre à la maquette de passer les vérifications de compilation.


Voici un test mis à jour:

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