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;