React Native 0.56 + Enzyme + Jest + React Navigation: Enzyme crashes because of import statement

三世轮回 提交于 2019-12-08 06:24:20

问题


TL;DR: My tests crash, because of the following error linked to 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)

The weird thing is that I use a named export, which is why React Navigation shouldn't even be loaded.

I'm trying to write unit tests for my login form. Here is the code for the form:

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

And here is my test file:

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

As you can see I'm using a named export to split the LoginForm from the ConnectedLoginForm which is connected to Redux and React Navigation. And still some part of Enzyme or Jest does not like React Navigation. Do you have any idea how to circumvent this problem?

Edit 1

I managed to find a workaround using a NavigationService. It would still be awesome to know how to fix this, because this bug also prevents me from testing my screens that are using React Navigation.

Edit 2

For anyone wondering how to mock this, what I ended up doing was creating a __mocks__ folder adjacent to the node_modules folder. In it I created a file called react-navigation.js in which I mocked all the behavior I needed to mock.

In the case of withNavigation() this meant just implementing a dummy HOC:

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

回答1:


You could mock dependencies, such as navigation, like this:

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

And then pass props, including navigation, to your component manually:

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


来源:https://stackoverflow.com/questions/51241827/react-native-0-56-enzyme-jest-react-navigation-enzyme-crashes-because-of

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!