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






Да, именно так вы бы издевались над свойством navigation, как и над любым другим свойством. Я так делаю в своих проектах.
Одно предложение: не высмеивайте каждое свойство. Измените только те, которые вам действительно нужны в вашем компоненте, и они будут вызваны.
Ой, извините, не заметил, что вы используете TypeScript. Я не использую его, но я действительно думаю, что это правильное время ожидания, чтобы это сделать. Вы всегда будете передавать опору методами. Вы не можете издеваться над теми, которые не будете использовать, и просто определите такие, как этот navigate: () => {}
Верно, но мне больше нравится этот способ издевательства над ним, поскольку, если он мне нужен, я могу просто использовать этот макет для всего. Я надеялся, что, возможно, есть способ автоматизировать объект в Jest с помощью TypeScript, поскольку TypeScript знает о свойствах объекта. Может быть, кто-то, кто разбирается в TypeScript, предложит лучшее решение. В любом случае, спасибо за ваш вклад!
Что касается компонентов, вы можете имитировать их для всех тестов с помощью установочного файла. Что касается компонентов, я так не считаю, используя или нет TypeScript. Я не знаю, сможете ли вы создать тип для навигации и имитировать этот тип ... Если это возможно, это наверняка лучшее решение. Но поскольку я не использую TypeScript, я не могу сказать, что это правильно или что он будет работать.
Мок не соответствует ожидаемому типу, поэтому 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
});
});
});
Хороший руководство, кстати. Я использовал его, чтобы настроить среду, чтобы посмотреть на этот вопрос ?
Спасибо! Это забавно, потому что, я помню, вы помогли мне с другим важным вопросом, который у меня возник. Я собираюсь поделиться с вами вашими знаниями! ?
лол, звучит хорошо. Знания на самом деле не «мои», поэтому распространяйте их ... знания работают лучше всего, когда ими делятся ?
Так что насчет функциональных компонентов?
Я не очень доволен своим решением, но я начал издеваться только над теми свойствами 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», используемое в определении навигационной опоры, может быть заменено на любое, я просто не люблю их использовать, если мне не нужно.
Вы можете использовать основы этого фрагмента кода для конкретного сценария, изложенного в вопросе.
Поскольку я использую TypeScript, это не сработает. Если я не буду издеваться над каждым свойством, TypeScript будет жаловаться, что компоненты ожидают порп другого типа.