【问题标题】:React Native 0.56 + Enzyme + Jest + React Navigation: Enzyme crashes because of import statementReact Native 0.56 + Enzyme + Jest + React Navigation:Enzyme 因 import 语句而崩溃
【发布时间】:2018-12-16 22:02:06
【问题描述】:

TL;DR:我的测试崩溃了,因为以下与 React Navigation 相关的错误:

/path-to-app/react-native/myApp/node_modules/react-navigation/src/views/withNavigation.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import React from 'react';
                                                                                             ^^^^^^
SyntaxError: Unexpected token import

      127 | }
      128 |
    > 129 | export default withNavigation(
          |                                ^
      130 |   connect(
      131 |     null,
      132 |     { loginSuccess }

      at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:403:17)
      at Object.get withNavigation [as withNavigation] (node_modules/react-navigation/src/react-navigation.js:166:12)
      at Object.<anonymous> (app/components/LoginForm/LoginForm.js:129:32)

奇怪的是我使用了一个命名的导出,这就是为什么甚至不应该加载 React Navigation。

我正在尝试为我的登录表单编写单元测试。这是表单的代码:

import React, { Component } from "react";
import { View } from "react-native";
import { connect } from "react-redux";
import { Formik } from "formik";
import { object as yupObject, string as yupString } from "yup";
import { withNavigation } from "react-navigation";
import PropTypes from "prop-types";

import { loginSuccess } from "../../actions/login/login";
import alert from "../../api/alert/alert";
import { apiLoginUser } from "../../api/auth/auth";
import {
  BUTTON_TEXT_LOGIN,
  BUTTON_TEXT_FORGOTTEN_PASSWORD
} from "../../config/constants/buttonTexts";
import {
  ERROR_MESSAGE_INVALID_EMAIL_FORMAT,
  ERROR_MESSAGE_EMAIL_REQUIRED,
  ERROR_MESSAGE_PASSWORD_REQUIRED,
  ERROR_MESSAGE_PASSWORD_MIN_LENGTH
} from "../../config/constants/errorMessages";
import AuthInput from "../AuthInput/AuthInput";
import Button from "../Button/Button";
import ClearButton from "../ClearButton/ClearButton";
import styles from "./styles";

export class LoginForm extends Component {
  static propTypes = {
    navigation: PropTypes.object,
    loginSuccess: PropTypes.func,
    isSubmitting: PropTypes.bool
  };

  handleSubmit = (values, formikBag) => {
    formikBag.setSubmitting(true);
    apiLoginUser(values.email, values.password)
      .then(data => {
        this.props.navigation.navigate("HomeScreen");
        formikBag.setSubmitting(false);
        this.props.loginSuccess(data.user);
      })
      .catch(error => {
        alert(error);
        formikBag.setSubmitting(false);
      });
  };

  renderForm = (
    values,
    handleSubmit,
    setFieldValue,
    errors,
    touched,
    setFieldTouched,
    isValid,
    isSubmitting
  ) => (
    <View style={styles.inputContainer}>
      <AuthInput
        placeholder="Email address"
        value={values.email}
        onChange={setFieldValue}
        onTouch={setFieldTouched}
        name="email"
        error={touched.email && errors.email}
        editable={!isSubmitting}
      />
      <AuthInput
        placeholder="Password"
        value={values.password}
        onChange={setFieldValue}
        onTouch={setFieldTouched}
        name="password"
        error={touched.password && errors.password}
        editable={!isSubmitting}
        secureTextEntry
      />
      <ClearButton
        text={BUTTON_TEXT_FORGOTTEN_PASSWORD}
        onPress={() => {}}
        containerStyles={styles.clearButtonContainer}
        buttonTextStyles={styles.clearButtonText}
      />
      <Button onPress={handleSubmit} disabled={!isValid || isSubmitting} loading={isSubmitting}>
        {BUTTON_TEXT_LOGIN}
      </Button>
    </View>
  );

  render() {
    return (
      <Formik
        initialValues={{ email: "", password: "" }}
        onSubmit={this.handleSubmit}
        validationSchema={yupObject().shape({
          email: yupString()
          .email(ERROR_MESSAGE_INVALID_EMAIL_FORMAT)
          .required(ERROR_MESSAGE_EMAIL_REQUIRED),
          password: yupString()
          .min(12, ERROR_MESSAGE_PASSWORD_MIN_LENGTH)
          .required(ERROR_MESSAGE_PASSWORD_REQUIRED)
        })}
        render={({
          values,
          handleSubmit,
          setFieldValue,
          errors,
          touched,
          setFieldTouched,
          isValid,
          isSubmitting
        }) =>
          this.renderForm(
            values,
            handleSubmit,
            setFieldValue,
            errors,
            touched,
            setFieldTouched,
            isValid,
            isSubmitting
          )
        }
      />
    );
  }
}

export default withNavigation(
  connect(
    null,
    { loginSuccess }
  )(LoginForm)
);

这是我的测试文件:

import React from "react";
import { View } from "react-native";
import { shallow } from "enzyme";

import { LoginForm } from "./LoginForm";

describe("LoginForm", () => {
  describe("rendering", () => {
    let wrapper;
    beforeEach(() => {
      wrapper = shallow(<LoginForm />);
    });

    it("should render a <View />", () => {
      expect(wrapper.find(View)).toHaveLength(1);
    });
  });
});

如您所见,我使用命名导出将LoginForm 与连接到 Redux 和 React Navigation 的ConnectedLoginForm 分开。还有一部分 Enzyme 或 Jest 不喜欢 React Navigation。你知道如何规避这个问题吗?

编辑 1

我设法找到了workaround using a NavigationService. 知道如何解决这个问题仍然很棒,因为这个错误还阻止我测试使用 React Navigation 的屏幕。

编辑 2

对于任何想知道how to mock this 的人,我最终做的是在node_modules 文件夹旁边创建一个__mocks__ 文件夹。在其中我创建了一个名为 react-navigation.js 的文件,我在其中模拟了我需要模拟的所有行为。

withNavigation() 的情况下,这意味着只是实现一个虚拟HOC

export const withNavigation = () => WrappedComponent => WrappedComponent;

【问题讨论】:

    标签: reactjs react-native jestjs react-navigation enzyme


    【解决方案1】:

    您可以像这样模拟依赖项,例如导航:

    jest.mock('react-navigation', () => ({ withNavigation: component => component}));
    

    然后手动将道具(包括导航)传递给您的组件:

    const mockProps = {
        navigation: { navigate: jest.fn() },
        loginSuccess: jest.fn(),
        isSubmitting: true
    }
    wrapper = shallow(<LoginForm {...mockProps}/>);
    

    【讨论】:

    • 这似乎适用于使用 withNavigation() 的组件。我将如何处理实际的屏幕?
    • 将您的场景保存在与您创建导航器的位置不同的文件中。然后你可以像普通组件一样测试它们。对于导航创建,只需模拟 createStackNavigator 或任何导航器,并断言它被调用的内容。
    • 哇非常感谢您的帮助!我会对此进行测试并回复您:)
    • 我在没有酶的情况下进行了快照测试:const mockProps = { navigation: { navigate: jest.fn() }, } const tree = renderer.create(&lt;MyScreen {...mockProps}/&gt;).toJSON(); expect(tree).toMatchSnapshot(); });
    猜你喜欢
    • 1970-01-01
    • 2017-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-16
    • 2017-07-16
    • 1970-01-01
    • 2020-03-09
    相关资源
    最近更新 更多