如何在 React Native 中使用 TypeScript 为单元测试模拟 React Navigation 的导航道具?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/52569447/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 05:40:27  来源:igfitidea点击:

How to mock React Navigation's navigation prop for unit tests with TypeScript in React Native?

typescriptunit-testingreact-nativejestjsreact-navigation

提问by J. Hesters

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.

我正在使用 TypeScript 构建一个 React Native 应用程序。对于我的导航,我使用 React Navigation,对于我的单元测试,我使用 Jest 和 Enzyme。

Here is the (stripped down) code for one of my screen (LoadingScreen.tsx):

这是我的一个屏幕 (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 navigationprop. Here is the testing file's code (LoadingScreen.test.tsx):

在尝试测试屏幕时,我遇到了一个问题。屏幕需要一个类型为 NavigiationScreenProps 的道具,因为我正在访问 React Navigationsnavigation道具。这是测试文件的代码 (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 LoadingScreenexpects a navigationprop.

问题是,这LoadingScreen需要一个navigation道具。

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 navigationprop. I tried doing that (as you can see I imported *from React Navigation in my test), but couldn't figure out. There is only NavigationActionsthat is remotely useful but it only includes navigate(). TypeScript expects everything, even the state, to be mocked. How can I mock the navigationprop?

我想我必须以某种方式嘲笑navigation道具。我尝试这样做(如您所见,我*在测试中从 React Navigation导入),但无法弄清楚。只有NavigationActions远程有用的,但它只包括navigate(). TypeScript 期望所有内容,甚至状态,都可以被模拟。我怎样才能嘲笑navigation道具?

Edit 1:Is the approach of using NavigationScreenPropseven correct or should I use the interface Propsapproach? If yes how would you then mock than (it results in the same error).

编辑1:是使用的方法NavigationScreenProps,即使正确或我应该使用interface Props的方法呢?如果是,那么您将如何模拟(它会导致相同的错误)。

Edit 2:Using the second approach with the interface and

编辑 2:在界面中使用第二种方法和

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.

编辑 3:当我使用 JS 时,只模拟我需要的属性就足够了(通常只是导航)。但是自从我开始使用 TypeScript,我不得不模拟导航的每一个方面。否则 TypeScript 会抱怨组件需要一个不同类型的 prop。

回答by Brian Adams

Issue

问题

The mock does not match the expected type so TypeScript reports an error.

模拟与预期类型不匹配,因此 TypeScript 报告错误。

Solution

解决方案

You can use the type any"to opt-out of type-checking and let the values pass through compile-time checks".

您可以使用类型any“选择退出类型检查并让值通过编译时检查”

Details

细节

As you mentioned, in JavaScript it works to mock only what is needed for the test.

正如您所提到的,在 JavaScript 中,它只模拟测试所需的内容。

In TypeScript the same mock will cause an error since it does not completely match the expected type.

在 TypeScript 中,相同的模拟会导致错误,因为它与预期的类型不完全匹配。

In situations like these where you have a mock that you know does not match the expected type you can use anyto allow the mock to pass through compile-time checks.

在这种情况下,您知道一个模拟与预期类型不匹配,您可以使用它any来允许模拟通过编译时检查。



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

回答by luissmg

Yes, that's how you would mock the navigationproperty, like any other property. I do it like this in my projects.

是的,这就是您模拟navigation属性的方式,就像任何其他属性一样。我在我的项目中这样做。

One suggestion: Don't mock every single property. Mock only the ones you really need in your component and that will be called.

一个建议:不要模拟每一个属性。仅模拟您在组件中真正需要的那些,并且会被调用。