Testing Asynchronous React Forms with Formik and Testing Library

Testing Asynchronous React Forms with Formik and Testing Library

Testing Asynchronous React Forms with Formik and Testing Library. Formik is a library that makes creating complex forms a snap. Testing Library (previously known as React Testing Library) is the gold standard when it comes to testing React applications.

Formik is a library that makes creating complex forms a snap. Testing Library (previously known as React Testing Library) is the gold standard when it comes to testing React applications. While working with Formik for the last couple of years, I have found that some developers are not comfortable with testing it.

For this exercise, we will make a simple React application using create-react-app that renders a login form, has some simple validation and makes an API call to verify the username and password. If the API tells us that the username and password are correct, our application should redirect the user to another page. If incorrect, we should display a message to the user to that effect.

Building our application

We can start by bootstrapping a new React application with create-react-app. I decided to use the TypeScript template.

npx create-react-app my-app --template typescript

** NOTE **

Before getting started, make sure you update all of your dependencies to their latest versions. At the time of writing this article, there was a problem with using Create React App with testing library. To resolve this problem, I had to install jest-environment-jsdom-sixteen and update my test script.

"test": "react-scripts test --env=jest-environment-jsdom-sixteen"

Next, I created a file called LoginForm, and added the following.

import React from "react";
import cx from "classnames";
import { Formik } from "formik";

export const LoginForm = () => {
  return (
    <Formik
      initialValues={{ email: "", password: "" }}
      validate={values => {
        const errors: Record<string, string> = {};
        if (!values.email) {
          errors.email = "Required";
        } else if (
          !/^[A-Z0-9._%+-][email protected][A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
        ) {
          errors.email = "Invalid email address";
        }
        if (!values.password) {
          errors.password = "Required";
        }
        return errors;
      }}
      onSubmit={async (values, { setStatus, setSubmitting }) => {
        setSubmitting(true);
        try {
          const response = await fetch("/api/sign-in", {
            method: "POST", // or 'PUT'
            headers: {
              "Content-Type": "application/json"
            },
            body: JSON.stringify(values)
          });

          if (!response.ok) {
            throw Error(response.statusText);
          }

          window.location.replace("/dashboard");
        } catch (e) {
          console.log("e", 
          e);
          const apiError = {
            apiError: "Invalid username or password"
          };
          setStatus(apiError);
        } finally {
          setSubmitting(false);
        }
      }}
    >
      {({
        values,
        status,
        errors,
        touched,
        handleChange,
        handleBlur,
        handleSubmit,
        isSubmitting
        /* and other goodies */
      }) => {
        const hasError = Object.keys(errors).length > 0;
        return (
          <div className="w-full max-w-xs">
            {status && status["apiError"] && (
              <div
                className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4"
                role="alert"
              >
                 {status["apiError"]}
              </div>
            )}

            <form
              className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"
              onSubmit={handleSubmit}
            >
              <div className="mb-4">
                <label
                  className="block text-gray-700 text-sm font-bold mb-2"
                  htmlFor="email"
                >
                  Email
                </label>
                <input
                  className={cx(
                    "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline",
                    {
                      "border-red-500": touched["email"] && errors["email"]
                    }
                  )}
                  id="email"
                  name="email"
                  type="text"
                  placeholder="Email"
                  value={values.email}
                  onChange={handleChange}
                  onBlur={handleBlur}
                />
                {touched["email"] && errors["email"] && (
                  <p className="pt-2 text-red-500 text-xs italic">
                    {errors["email"]}
                  </p>
                )}
              </div>
              <div className="mb-6">
                <label
                  className="block text-gray-700 text-sm font-bold mb-2"
                  htmlFor="password"
                >
                  Password
                </label>
                <input
                  className={cx(
                    "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline",
                    {
                      "border-red-500": touched["email"] && errors["email"]
                    }
                  )}
                  id="password"
                  name="password"
                  type="password"
                  value={values.password}
                  onChange={handleChange}
                  onBlur={handleBlur}
                  placeholder="******************"
                />
                {touched["password"] && errors["password"] && (
                  <p className="text-red-500 text-xs italic">
                    {errors["password"]}
                  </p>
                )}
              </div>
              <div className="flex items-center justify-between">
                <button
                  disabled={isSubmitting || hasError}
                  className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
                  type="submit"
                >
                  Sign In
                </button>
                <a
                  className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
                  href="#"
                >
                  Forgot Password?
                </a>
              </div>
            </form>
            <p className="text-center text-gray-500 text-xs">
              ©2020 Acme Corp. All rights reserved.
            </p>
          </div>
        );
      }}
    </Formik>
  );
};

There’s a lot going on in here, so let’s just concentrate on a few things.

  • We use Tailwind for some quick and dirty CSS styling.
  • We validate the email and password fields on the client-side and show errors if they are touched.
  • We call the API in our submit function, and use setStatus from Formik if we get an error, otherwise we use window.location.replace to redirect the user.

After playing around with our form in the browser, we can start by writing our first tests, but first, we need to decide what to test.

react javascript programming testing developer

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

How native is React Native? | React Native vs Native App Development

Article covers: How native is react native?, React Native vs (Ionic, Cordova), Similarities and difference between React Native and Native App Development.

Hire Dedicated React Native Developer

Have you ever thought of having your own app that runs smoothly over multiple platforms? React Native is an open-source cross-platform mobile application framework which is a great option to create mobile apps for both Android and iOS. **[Hire...

Hire Dedicated React Native Developer in India | React Native Development

Hire dedicated React Native developers for your next project. As the top react native development company we offer you the best service as per your business needs.

Hire React Js Developer from Expert React JS Development Company

Are you looking to [hire React JS developer](https://www.mobiwebtech.com/react-js-development-company/ "hire React JS developer") from a prestigious and reliable React JS development company? Visit at Mobiweb Technologies here we have a big team...

A Short Guide to React Native App Development

React Native is undoubtedly one of the most widely used cross-platform frameworks for creating native-like apps. This framework can be easily used for developing brand-new apps from scratch and even in existing iOS or Android projects.easily used for developing brand-new apps from scratch and even in existing iOS or Android projects.