Search code examples
reactjstypescriptjestjsintegration-testingreact-testing-library

Testing Formik onSubmit function with react-testing-library


I'm trying write integration tests the onSubmit function of a Formik form using Jest and React Testing Library. Currently, I have a LoginForm component which functional right now, but I'm having trouble testing it. I'm referencing the example for Formik in React Testing Library's docs and it looks like they're using a mock function to test the onSubmit function.

https://testing-library.com/docs/example-react-formik/

It looks like onSubmit is passed in via props to the component, but my component isn't doing that right now and I'm wondering if there's a way to test my onSubmit function as it's currently written? Should I update my code to be functional with the tests? Any direction will be appreciated.

import React from "react";
import { Field, Form, Formik } from "formik";

import { loginService } from "../../services/login/loginService";
import { yupValidationSchema } from "./yupValidationSchema";

const LoginForm = () => {
  const [state, setState] = React.useState({
    loginFailed: false, // Boolean represented as true when user is successfully authenticated,
                       // false when there's an auth error, token has been removed or user has been logged out
  });

  const handleSubmit = async (values: { username: string; password: string }) => {
    const loginSuccess = await loginService(values.username, values.password);

    if (loginSuccess) {
      window.history.replaceState("#/login", "", "#/");
      window.location.reload();
    } else {
      setState({
        ...state,
        loginFailed: true,
      });
    }
  };

  return (
      <Formik
        initialValues={{ username: "", password: "" }}
        validationSchema={yupValidationSchema}
        onSubmit={handleSubmit}
      >
        {({ errors, touched }) => (
          <Form>
            <Field data-testid={"username"} name="username" type="text" placeholder="Username" />
            {touched.username && errors.username && <div>{errors.username}</div>}
            <Field
              data-testid={"password"}
              name="password"
              type="password"
              placeholder="Password"
            />{" "}
            <br />
            {touched.password && errors.password && <div>{errors.password}</div>}
            <button data-testid={"button"} name="submitButton" type="submit">
              SUBMIT
            </button>
            {state.loginFailed && <div data-testid={"loginError"}>Unsuccessful Login</div>}
          </Form>
        )}
      </Formik>
  );
};

export default LoginForm;

describe("Login Component", () => {

  const handleSubmit = jest.fn(); // where do I pass this?

  test("Test Submission of login form" , async () => {
    act(() => {
      render(
          <LoginForm /> // do I pass props in here?
      ); 
    });

    const username = screen.getByPlaceholderText(/Username/i);
    const password = screen.getByPlaceholderText(/Password/i);
    const submitButton = screen.getByText("SUBMIT");


    await waitFor(() => {
      fireEvent.change(username, {
        target: {
          value: "testUser",
        },
      });
    });

    await waitFor(() => {
      fireEvent.change(password, {
        target: {
          value: "testPassword",
        },
      });
    });

    await waitFor(() => {
      fireEvent.click(submitButton);
    });


    await waitFor(() => {
    
      expect(handleSubmit).toHaveBeenCalledWith({ // how do I test the handle submit?
        username: "testUser",
        password: "testPassword",
      })
    });
  });
});



Solution

  • You can test if the component behaves as expected. So looking at the code of handleSubmit method (see comments):

    const loginSuccess = await loginService(values.username, values.password); // mock this and check if this is called with expected parameters
    
    if (loginSuccess) { // mock the loginService to return truthy value and check if the redirection is made
      window.history.replaceState("#/login", "", "#/");
      window.location.reload();
    } else { // mock the loginService to return falsy value and check for element with error message: "Unsuccessful Login"
      setState({
        ...state,
        loginFailed: true,
      });
    }
    

    With that test you should also achieve nice coverage :)