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

J. *_*ers 13 unit-testing typescript jestjs react-native react-navigation

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

这是我的一个屏幕(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;
Run Code Online (Sandbox Code Playgroud)

在尝试测试屏幕时,我遇到了一个问题.屏幕需要一个具有NavigiationScreenProps类型的道具,因为我正在访问React Navigations navigation道具.这是测试文件的代码(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);
    });
  });
});
Run Code Online (Sandbox Code Playgroud)

问题是,LoadingScreen期望一个navigation道具.

我收到错误:

[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
Run Code Online (Sandbox Code Playgroud)

我怎样才能解决这个问题?

我想我不得不嘲笑navigation道具.我试过这样做(正如你可以看到我*在我的测试中从React Navigation 导入的),但是无法理解.只有NavigationActions远程有用但它只包括navigate().TypeScript期望模拟所有内容,甚至是状态.我怎么能嘲笑navigation道具呢?

编辑1:使用方法NavigationScreenProps是否正确或我应该使用这种interface Props方法?如果是,你会如何模拟(它导致相同的错误).

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

export class LoadingScreen extends Component<Props, object>
Run Code Online (Sandbox Code Playgroud)

我能够"解决"这个问题.我真的必须像这样模拟导航对象的每个属性:

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
});
Run Code Online (Sandbox Code Playgroud)

问题仍然存在:这是正确的吗?或者有更好的解决方案吗?

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

Bri*_*ams 18

问题

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

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

细节

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

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

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


这是一个更新的测试:

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
    });
  });
});
Run Code Online (Sandbox Code Playgroud)

  • 哈哈,听起来不错。这些知识并不是真正“我的”,所以要传播它……知识在分享时效果最好 (3认同)

Lee*_*ley 6

对于 Typescript 用户来说,另一种方法只是为了兴趣。

对于那些不喜欢使用任何类型的人,这里有一个利用 Typescript 的部分实用程序类型和类型断言(使用“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}/>);
    });
});
Run Code Online (Sandbox Code Playgroud)

使用 Partial,我们可以享受我们的编辑器/IDE 智能感知功能,导航属性定义中使用的类型断言“as”可以替换为 any,如果不需要,我只是不喜欢使用 any。

您可以在此代码片段中的基础知识的基础上构建问题中列出的特定场景。