web-dev-qa-db-ja.com

React NativeのTypeScriptを使用した単体テストのReact Navigationのナビゲーションプロパティをモックする方法は?

TypeScriptを使用してReactネイティブアプリを構築しています。ナビゲーションにはReactナビゲーションを使用し、ユニットテストにはJestとEnzymeを使用します。

以下は、私の画面の1つ(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;
_

画面をテストしようとしたときに問題に遭遇しました。 React Navigations navigation propにアクセスしているため、画面にはNavigiationScreenPropsタイプのプロップが必要です。テストファイルのコード(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);
    });
  });
});
_

問題は、LoadingScreennavigation小道具を期待していることです。

エラーが表示されます:

_[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
_

どうすれば修正できますか?

どういうわけか、navigationプロップをモックする必要があると思います。私はそれをやってみました(あなたが見ることができるように、私のテストでReact Navigationから_*_をインポートしました)がわかりませんでした。リモートで有用なNavigationActionsのみがありますが、含まれるのはnavigate()のみです。 TypeScriptは、状態も含め、すべてがモックされることを期待しています。 navigationプロップをどのようにモックできますか?

編集1:NavigationScreenPropsを使用するアプローチは正しいですか、それとも_interface Props_アプローチを使用する必要がありますか?はいの場合、どのようにモックしますか(同じエラーになります)。

編集2:インターフェイスで2番目のアプローチを使用し、

_export class LoadingScreen extends Component<Props, object>
_

この問題を「解決」することができました。文字通り、ナビゲーションオブジェクトのすべてのプロパティを次のようにモックする必要がありました。

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

問題は残っています:これは正しいですか?または、より良い解決策はありますか?

編集3: JSを使用したとき、必要なプロパティのみをモックするだけで十分でした(多くの場合、単にナビゲートします)。しかし、TypeScriptの使用を開始してから、ナビゲーションのあらゆる側面をモックする必要がありました。そうしないと、TypeScriptは、コンポーネントが異なるタイプの小道具を期待していると文句を言います。

12
J. Hesters

問題

モックは期待されるタイプと一致しないため、TypeScriptはエラーを報告します。

溶液

タイプany"タイプチェックをオプトアウトし、値がコンパイル時チェックを通過するようにする" を使用できます。

詳細

既に述べたように、JavaScriptでは、テストに必要なものだけをモックするように機能します。

TypeScriptでは、同じモックが期待されるタイプと完全に一致しないため、エラーが発生します。

予想される型と一致しないモックがあるような状況では、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
    });
  });
});
17