J. Hesters
J. Hesters

Reputation: 14824

How to test a component is given the parent component's function in React (Native) with Jest and Enzyme?

I'm building a React Native app and I'm using Jest and Enzyme for my unit tests. Furthermore, I'm using TypeScript.

I build the following form using Formik.

import strings from "./strings";
import styles from "./styles";
import { strings as loginStrings } from "../../screens/Login";
import { Formik, FormikActions, FormikProps } from "formik";
import React, { Component } from "react";
import { View } from "react-native";
import { Button, Input } from "react-native-elements";
import { NavigationScreenProp } from "react-navigation";
import { object as yupObject, string as yupString } from "yup";

export interface FormValues {
  email: string;
  password: string;
}

export interface Props {
  navigation: NavigationScreenProp<any, any>;
}

export default class LoginForm extends Component<Props, object> {
  handleSubmit = (values: FormValues, formikBag: FormikActions<FormValues>) => {
    // ... api calls and stuff
  };

  renderForm = ({
    values,
    handleSubmit,
    setFieldValue,
    touched,
    errors,
    setFieldTouched,
    isValid,
    isSubmitting
  }: FormikProps<FormValues>) => (
    <View style={styles.container}>
      // ... two inputs and a button
    </View>
  );

  render() {
    return (
      <Formik
        initialValues={{ email: "", password: "" }}
        onSubmit={(values: FormValues, formikBag: FormikActions<FormValues>) =>
          this.handleSubmit(values, formikBag)
        }
        validationSchema={yupObject().shape({
          email: yupString()
            .email(strings.invalidEmailFormat)
            .required(strings.emailRequired),
          password: yupString()
            .min(8, strings.passwordMinLength)
            .required(strings.passwordRequired)
        })}
        render={(formikBag: FormikProps<FormValues>) => this.renderForm(formikBag)}
      />
    );
  }
}

As you can see it is just a simple form. I now want to test that the <Formik /> component get's passed renderForm() and handleSubmit, so I wrote the following test:

it("should be passed the component's handleSubmit function for onSubmit", () => {
  expect(wrapper.find("Formik").prop("onSubmit")).toEqual(
    (values: FormValues, formikBag: FormikActions<FormValues>) =>
      wrapper.instance().handleSubmit(values, formikBag)
  );
});

And same for renderForm(). Unfortunately this throws an error:

● LoginForm › rendering › should be passed the component's handleSubmit function for onSubmit

    expect(received).toEqual(expected)

    Expected value to equal:
      [Function anonymous]
    Received:
      [Function onSubmit]

    Difference:

    - Expected
    + Received

    - [Function anonymous]
    + [Function onSubmit]

      28 |
      29 |     it("should be passed the component's handleSubmit function for onSubmit", () => {
    > 30 |       expect(wrapper.find("Formik").prop("onSubmit")).toEqual(
         |                                                       ^
      31 |         (values: FormValues, formikBag: FormikActions<FormValues>) =>
      32 |           wrapper.instance().handleSubmit(values, formikBag)
      33 |       );

So I'm unsure how I can correctly test that the function has been past to the component. How would one do this?

There was one I way which I found to work, which was to pass the functions to <Formik /> like this:

onSubmit={this.handleSubmit}
render={this.renderForm}

And then just:

expect(wrapper.find("Formik").prop("onSubmit)).toEqual(wrapper.instance().onSubmit)

The thing is I had trouble with my unit tests in the past, when I just passed in the function like that. Also this way I loose the types of TypeScript. Is there a way to make my initial attempt work"

Upvotes: 1

Views: 1632

Answers (1)

ᴘᴀɴᴀʏɪᴏᴛɪs
ᴘᴀɴᴀʏɪᴏᴛɪs

Reputation: 7529

When you are defining that anonymous function, you are essentially creating a new one so it will never be equal to the one that the render uses internally. Your options are to store it as part of the object and check for reference equality (which is what you suggested works):

expect(wrapper.find("Formik").prop("onSubmit)).toEqual(wrapper.instance().onSubmit)

The other option is to mock handleSubmit, and make your test check whether handleSubmit gets called when you click on the Submit button, which is in my opinion a more reasonable test.

// set handleSubmit to our fake mock function
wrapper.instance().handleSubmit = jest.fn();
wrapper.update();
// Here you need to simulate a click to submit form
// I don't know how your input looks like
// but it will be similar to
inp.simulate('click')
// expect our mock function to have been called at least once
expect(wrapper.instance().handleSubmit).toHaveBeenCalledTimes(1);

Upvotes: 1

Related Questions