Как издеваться над навигационной опорой React Navigation для модульных тестов с TypeScript в React Native?

Я создаю приложение React Native с TypeScript. Для навигации я использую 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;

При попытке протестировать экраны столкнулся с проблемой. Экраны ожидают опору с типом 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);
    });
  });
});

Проблема в том, что 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

Как я могу это исправить?

Думаю, мне нужно как-то поиздеваться над опорой navigation. Я пробовал это сделать (как видите, в своем тесте я импортировал * из React Navigation), но не смог понять. Есть только NavigationActions, который можно использовать удаленно, но он включает только navigate(). TypeScript ожидает, что над всем, даже над государством, будут издеваться. Как я могу издеваться над опорой navigation?

Изменить 1: Правильный ли подход с использованием NavigationScreenProps или мне следует использовать подход interface Props? Если да, то как бы вы издевались над чем (это приводит к той же ошибке).

Изменить 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 будет жаловаться, что компонент ожидает опору с другим типом.

Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
34
0
23 003
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Да, именно так вы бы издевались над свойством navigation, как и над любым другим свойством. Я так делаю в своих проектах.

Одно предложение: не высмеивайте каждое свойство. Измените только те, которые вам действительно нужны в вашем компоненте, и они будут вызваны.

Поскольку я использую TypeScript, это не сработает. Если я не буду издеваться над каждым свойством, TypeScript будет жаловаться, что компоненты ожидают порп другого типа.

J. Hesters 02.10.2018 12:49

Ой, извините, не заметил, что вы используете TypeScript. Я не использую его, но я действительно думаю, что это правильное время ожидания, чтобы это сделать. Вы всегда будете передавать опору методами. Вы не можете издеваться над теми, которые не будете использовать, и просто определите такие, как этот navigate: () => {}

luissmg 02.10.2018 12:52

Верно, но мне больше нравится этот способ издевательства над ним, поскольку, если он мне нужен, я могу просто использовать этот макет для всего. Я надеялся, что, возможно, есть способ автоматизировать объект в Jest с помощью TypeScript, поскольку TypeScript знает о свойствах объекта. Может быть, кто-то, кто разбирается в TypeScript, предложит лучшее решение. В любом случае, спасибо за ваш вклад!

J. Hesters 02.10.2018 13:04

Что касается компонентов, вы можете имитировать их для всех тестов с помощью установочного файла. Что касается компонентов, я так не считаю, используя или нет TypeScript. Я не знаю, сможете ли вы создать тип для навигации и имитировать этот тип ... Если это возможно, это наверняка лучшее решение. Но поскольку я не использую TypeScript, я не могу сказать, что это правильно или что он будет работать.

luissmg 02.10.2018 13:08
Ответ принят как подходящий

Проблема

Мок не соответствует ожидаемому типу, поэтому 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
    });
  });
});

Хороший руководство, кстати. Я использовал его, чтобы настроить среду, чтобы посмотреть на этот вопрос ?

Brian Adams 03.10.2018 05:23

Спасибо! Это забавно, потому что, я помню, вы помогли мне с другим важным вопросом, который у меня возник. Я собираюсь поделиться с вами вашими знаниями! ?

J. Hesters 04.10.2018 10:29

лол, звучит хорошо. Знания на самом деле не «мои», поэтому распространяйте их ... знания работают лучше всего, когда ими делятся ?

Brian Adams 07.10.2018 06:16

Так что насчет функциональных компонентов?

Miguel Coder 01.10.2020 13:52

Я не очень доволен своим решением, но я начал издеваться только над теми свойствами Navigation, которые необходимы для определенного компонента, например:

export function UserHomeScreen({
    navigation,
}: {
    navigation: {
        goBack: () => void;
        navigate: NavigationFunction<UserStackParams, "AddPhoneNumber" | "ValidatePhoneNumber">;
        setOptions: (options: { title: string }) => void;
    };
})

В тесте я могу предоставить это в качестве имитаций:

const goBack = jest.fn();
const navigate = jest.fn();
const setOptions = jest.fn();

renderer.create(<UserHomeScreen navigation = { { goBack, navigate, setOptions } } />)

Определение NavigationFunction также является полным ртом:

export type NavigationFunction<ParamsList, Routes extends keyof ParamsList> = <T extends Routes>(
    target: Routes,
    params?: ParamsList[T]
) => void;

Мне не удалось придумать правильную подпись для функций навигатора addListener и removeListener

Для пользователей 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}/>);
    });
});

Используя Partial, нам нравятся функции intellisense редактора / IDE, утверждение типа «as», используемое в определении навигационной опоры, может быть заменено на любое, я просто не люблю их использовать, если мне не нужно.

Вы можете использовать основы этого фрагмента кода для конкретного сценария, изложенного в вопросе.

Другие вопросы по теме

Похожие вопросы

Вызывать дочерний процесс через fork () при использовании ts-node
Импортированное изображение не имеет атрибута SRC, в то время как требуемое изображение получает странное имя
Хотите отображать больше данных в другом компоненте при нажатии кнопки детального просмотра (угловой 6)
Как преобразовать наблюдаемый результат в трубопроводный процесс
Аргументы универсального типа в элементах JSX с withStyles
Angular, как получить данные предыдущего просмотра при нажатии кнопки возврата браузера из другого представления
Неожиданный маркер; Ожидается "модуль, класс, интерфейс, перечисление, импорт или инструкция". в машинописном тексте
Как импортировать из '@somefolder' в angular
Как заставить TypeScript Array.map () возвращать то же самое, что и VanillaJS?
Чтобы взять объекты из массива, имеющего идентификатор во всех объектах, который хранится в массиве как пара значений ключа во всех объектах