DEV Community

Cover image for Testing a contact form with Cypress
Robin Pellegrims
Robin Pellegrims

Posted on • Originally published at pellegrims.dev on

Testing a contact form with Cypress

Introduction

Many websites have a dedicated contact form that allows users to send a message to the website owner. Using Cypress, we can easily write automated tests to prevent regressions to these types of forms.

Use case

For this article, we'll use a contact form containing three fields:

  • Name
  • E-Mail
  • Message

Contact Form screenshot {781x353}

Acceptance criteria

Before diving into writing the actual tests, it's usually a good idea to reflect on the acceptance criteria to determine what actually needs to be tested:

  • The contact form should be empty by default
  • The form can only be submitted if
    • All form fields are filled in
    • The e-mail address field contains a valid e-mail address
  • After the form has been successfully submitted
    • The fields should be hidden
    • A message should be shown saying the form was sent successfully
  • In case of an http error after submitting the form, an error message should be displayed and the fields should remain visible

Support functions

Based on these acceptance criteria, we can prepare some support functions to implement the desired checks and to make the actual test file more readable and maintainable. These functions can be added in the Cypress test file on top of your actual tests, or you could add them in specific support file.

Retrieving the fields DOM elements

Since our contact form already contains ids for each field, the field elements can easily be retrieved:

type ChainableInputElement = Cypress.Chainable<JQuery<HTMLInputElement>>;

export const getNameField = (): ChainableInputElement => cy.get('#field-Name');
export const getMailField = (): ChainableInputElement => cy.get('#field-Email');
export const getMessageField = (): ChainableInputElement =>
  cy.get('#field-Message');
export const getForm = () => cy.get('form');

Enter fullscreen mode Exit fullscreen mode

The reason we explicitly define the return type for each of the query functions is that by default, cy.get returns a Chainable HTMLElement, which doesn't allow us to chain with other handy functions like type.

Filling out the form

We'll need to be able to fill out the form with some valid dummy values:

export const completeForm = () => {
  getNameField().type('John Doe');
  getMailField().type('john.doe@mail.com');
  getMessageField().type('This is my message');
};

Enter fullscreen mode Exit fullscreen mode

Check validity of fields

The fields are all required so they might throw a validation error valueMissing. On top of that, the e-mail field might throw a typeMismatch validation error if the entered value is not a valid e-mail address.

export const assertValueMissing = (field: ChainableInputElement) =>
  assertValidity(field, { valueMissing: true });

export const assertTypeMismatch = (field: ChainableInputElement) =>
  assertValidity(field, { typeMismatch: true });

const assertValidity = (
  field: ChainableInputElement,
  expectedValidity: { valueMissing?: boolean; typeMismatch?: boolean }
) =>
  field.invoke('prop', 'validity').should('deep.include', {
    valueMissing: false,
    typeMismatch: false,
    ...expectedValidity,
  });

Enter fullscreen mode Exit fullscreen mode

Form fields visibility

The following function verifies if the fields are visible or hidden:

export const assertFieldsVisible = (visible: boolean) =>
  cy.get('input').should(visible ? 'have.length.gt' : 'have.length', 0);

Enter fullscreen mode Exit fullscreen mode

Mocking the api call

When we submit the form using a POST request in a Cypress test, we don't want to actually send the form data to the api. Instead, we can use intercept to mock the api call and return a specific response.

Note that we store the intercepted call using an alias so that we can easily check later if the data was submitted or not.

const submitAlias = 'submit';

export const mockResponseStatusCode = (statusCode: number) =>
  cy
    .intercept('POST', 'http://localhost:4200/api/contact', { statusCode })
    .as(submitAlias);

Enter fullscreen mode Exit fullscreen mode

Checking that the form was submitted

To verify if the form data was submitted, the previously assigned alias can be used:

export const assertSubmitted = (submitted: boolean) =>
  cy.get(`@${submitAlias}`).should(submitted ? 'not.be.null' : 'be.null');

Enter fullscreen mode Exit fullscreen mode

Submitting the form

There is only one button on the page, so we can just click the first button found to submit the form:

export const clickSubmitButton = () => cy.get('button').click();

Enter fullscreen mode Exit fullscreen mode

Implementing the Cypress test

Now that we have all these support functions ready, we can easily implement the actual tests:

describe('contact', () => {
  beforeEach(() => cy.visit('/contact'));

  it('form fields should be empty by default', () =>
    [getNameField(), getMailField(), getMessageField()].forEach((field) =>
      field.should('be.empty')
    ));

  it('form fields should have errors for missing fields', () => {
    assertValueMissing(getNameField());
    assertValueMissing(getMailField());
    assertValueMissing(getMessageField());
  });

  it('email field should have an error for an incorrect email address', () => {
    getMailField().type('john.doe@');
    assertTypeMismatch(getMailField());
  });

  describe('when submitting an invalid form', () => {
    beforeEach(() => {
      mockResponseStatusCode(204);
      clickSubmitButton();
    });
    it('should not send a POST request to the APi', () =>
      assertSubmitted(false));
    it('should keep the fields visible', () => assertFieldsVisible(true));
  });

  describe('when submitting a valid form', () => {
    beforeEach(() => {
      completeForm();
      mockResponseStatusCode(204);
      clickSubmitButton();
    });
    it('should send a POST request to the APi', () => assertSubmitted(true));
    it('should display a success message', () =>
      getForm().should('contain', 'Message succesfully sent, thank you!'));
    it('should hide the form fields', () => assertFieldsVisible(false));
  });

  describe('when submitting a valid form returns an error response', () => {
    beforeEach(() => {
      completeForm();
      mockResponseStatusCode(400);
      clickSubmitButton();
    });
    it('should send a POST request to the APi', () => assertSubmitted(true));
    it('should display an error message', () =>
      getForm().should('contain', 'Something went wrong, try again later.'));
    it('should keep the fields visible', () => assertFieldsVisible(true));
  });
});

Enter fullscreen mode Exit fullscreen mode

Contact Form screenshot {397x419}

Summary

In this article, we demonstrated how to implement automated testing for a simple contact form using Cypress.

We first defined some acceptance criteria determining the requirements for the contact form. Based on these acceptance criteria, a number of support functions were implemented to keep the final test file more readable and maintainable. Finally, these support functions were used in the actual test file.

Top comments (0)