Cypress Tutorial for Beginners: Getting started with End to End Testing
Tags: javascript, testing, webdev
Published: 2020-01-27 10:14:54

---
title: "Cypress Tutorial for Beginners: Getting started with End to End Testing"
published: true
description: "End to End testing can be slow and boring. Cypress has changed the way we test. Learn how to test your applications in this tutorial."
tags: javascript, testing, webdev
canonical_url: https://www.valentinog.com/blog/cypress/
---

_End to End testing can be slow and boring. Cypress has changed the way we test. Learn how to test your applications in this tutorial._

## Cypress Tutorial for Beginners: requirements

To follow along you need a working installation of **Node.js** on your system. Also, a basic understanding of a newer version of JavaScript is a plus.

## What is Cypress? What is End to End Testing?

**End to End Testing**, or UI testing is one the many approaches for testing a web application.

An end to end test is supposed to check whether a web application works as expected or not, by testing the so called **user flow**.

Is End to End Testing important? Yes it is. But nobody likes E2E tests. They can be slow, cumbersome and expensive to write.

On the other hand **testing gives you confidence**. Would you ship a broken product to your users on purpose?

Enter **Cypress**: a Javascript End to End testing framework. It will make your life easier.

## Disclaimer

Before purists yell at me: I know the **subtle line between End to End Testing, UI testing, integration testing**, and so on.

And for you dear reader: **testing terminology is so blurry that I can't even ...**. If it's your first time with JavaScript testing I recommend reading [Jest Tutorial for Beginners: Getting Started With Jest for JavaScript Testing](https://www.valentinog.com/blog/jest/) for an introduction on unit testing and terminology.

When you're done come back here for End to End Testing.

## Cypress tutorial for beginners: setting up the project

To start off create a new folder, I called mine **cypress-tutorial**, move into it an initialize a new JavaScript project:

```bash
mkdir cypress-tutorial && cd $_
npm init -y
```

Inside this folder create two new files. An HTML document in **index.html**:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Cypress tutorial for beginners</title>
  </head>
  <body>
    <main>
      <form>
        <div>
          <label for="name">Name</label>
          <input type="name" required name="name" id="name" />
        </div>
        <div>
          <label for="email">Email</label>
          <input type="email" required name="email" id="email" />
        </div>
        <div>
          <label for="message">Your message</label>
          <textarea id="message" name="message" required></textarea>
        </div>
        <div>
          <button type="submit">SEND</button>
        </div>
      </form>
    </main>
  </body>
  <script src="form.js"></script>
</html>
``` 

It is an HTML form with a bunch on inputs and a textarea. Next up create a JavaScript file in **form.js** with a minimal logic for handling form submission:

```javascript
const form = document.forms[0];

form.addEventListener("submit", event => {
  event.preventDefault();
});
```

Note that I won't add styles to keep things simple. And with this simple project in place we're ready to **install Cypress**.

## Installing Cypress

To install Cypress, still in the project folder, run:

```bash
npm i cypress --save-dev
```

Give it a minute (it needs to download the binary) and then run:

```bash
node_modules/.bin/cypress open
```

Cypress will start for the first time and a bunch of new folder will appear in your project. You can remove the example folder, but look a moment into it because there are some nice examples.

![Cypress opens](https://thepracticaldev.s3.amazonaws.com/i/6d0kxygc0xdnt8p4mp3w.png)

Close the window for now and head over the next section.

## Starting the project

To serve the project on the local machine make sure to have a newer version of Node.js installed and then run:

```bash
npx serve
```

This will spin a development server at http://localhost:5000/. Head over the link and you should see our form:

![npx serve](https://thepracticaldev.s3.amazonaws.com/i/l3hrpioamvu906p2mfed.png)

**serve** is a nice NPM package for development. And now time to write our first test!

## Cypress tutorial for beginners: writing your first test

Create a new file in **cypress/integration/form.spec.js** and write your first block:

```javascript
describe("Form test", () => {
  //
});
```

**describe** is a Cypress method (borrowed from Mocha) for containing one or more **related tests**. Every time you start writing a new suite of tests for a functionality wrap it in a describe block. 

As you can see it takes two arguments: a string for describing the test suite and a **callback function for wrapping the actual test**.

Next up we're going to meet another function called **it** which is the actual test block:

```javascript
describe("Form test", () => {
  it("Can fill the form", () => {
    //
  });
});
```

If you know already Jest you may recall that it takes **it** or **test** interchangeably. That's not the case with Cypress. **it** is the only accepted block.

And now time for a smoke test! In the **it block** write:

```javascript
describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");
  });
});
```

Here **cy** is Cypress itself and **visit** is a Cypress method for browsing to a given path.

**get** instead is a method for **selecting elements in the page**. With this code we tell Cypress "go grab the form in the page". 

In a minute we'll see Cypress in action, but first, a bit of configuration!

## Configuring Cypress

To streamline things a bit we're going to configure Cypress. To begin with open up **package.json** and create a script named **e2e** pointing to the Cypress binary:

```json
  "scripts": {
    "e2e": "cypress open"
  },
```

Next up open **cypress.json** and configure the base url:

```json
{
  "baseUrl": "http://localhost:5000"
}
```

With this option we tell Cypress to visit our development url. (5000 is the default port for the **serve** package).

And now we're ready to launch your first test!

## Cypress tutorial for beginners: running the test

Ready? With the development server still running in a terminal:

```bash
npx serve
```

**open up another terminal** and run:

```bash
npm run e2e
```

You should see **Cypress opening a browser** and going through the page:

![First test passing](https://thepracticaldev.s3.amazonaws.com/i/vxg5q4thz63uxgzk8gzn.png)

That's your first test passing! Both **visit** and **get** are Cypress commands which act also as **implicit assertions**, that is, if the element is in the page Cypress will consider the test passed.

Now let's continue extending your test to see if the user can fill the form:

```javascript
describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");

    cy.get('input[name="name"]').type("Molly");
  });
});
```

Here's another Cypress command: **type**, which unsurprisingly types into our first text input. Also notice the CSS selector for getting the input element.

While there let's also add another command: **should**. This command creates an assertion and is used for example to check if an input is updating its state as expected:

```javascript
describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");

    cy.get('input[name="name"]')
      .type("Molly")
      .should("have.value", "Molly");
  });
});
```

Notice **have.value**. If you're new to this concept you can [learn more about assertions here](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Assertions).

And with a minimal test in place let's continue in the next section.

## Cypress tutorial for beginners: more tests and a submit

To continue our test we can check the email input:

```javascript
describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");

    cy.get('input[name="name"]')
      .type("Molly")
      .should("have.value", "Molly");

    cy.get('input[name="email"]')
      .type("molly@dev.dev")
      .should("have.value", "molly@dev.dev");
  });
});
```

Also we can type into the textarea:

```javascript
describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");

    cy.get('input[name="name"]')
      .type("Molly")
      .should("have.value", "Molly");

    cy.get('input[name="email"]')
      .type("molly@dev.dev")
      .should("have.value", "molly@dev.dev");

    cy.get("textarea")
      .type("Mind you if I ask some silly question?")
      .should("have.value", "Mind you if I ask some silly question?");
  });
});
```

**If you left Cypress open** the test should watch your changes and run automatically:

![More tests](https://thepracticaldev.s3.amazonaws.com/i/r9e6yc2oqzrfo438lbhh.png)

How nice! As icing on the cake let's **test form submission** with **submit**:

```javascript
describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");

    cy.get('input[name="name"]')
      .type("Molly")
      .should("have.value", "Molly");

    cy.get('input[name="email"]')
      .type("molly@dev.dev")
      .should("have.value", "molly@dev.dev");

    cy.get("textarea")
      .type("Mind you if I ask some silly question?")
      .should("have.value", "Mind you if I ask some silly question?");

    cy.get("form").submit();
  });
});
```

The test should keep passing without any problem. **One thing you can notice are these self-describing commands**: type, submit. It's plain english.

Now let's go a bit fancy in the next section with **XHR request testing**.

## Stubbing XHR requests with Cypress

Among all the things **Cypress is also able to intercept AJAX request**. This approach is called **stubbing**.

Stubbing is convenient when working in development where you can choose to **return fake response to your AJAX requests**.

To demonstrate this feature let's add a new piece of code inside our test:

```javascript
describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");
    
    // omitted for brevity

    cy.server();
    cy.route({
      url: "/users/**",
      method: "POST",
      response: { status: "Saved", code: 201 }
    });

    cy.get("form").submit();
  });
});
```

Here **cy.server** spins up a "virtual" server while **cy.route** is used for configuring a fake API endpoint. 

Now let's add another test to check things out: **after the user submits the form we want to test that the fake API is responding**. Why so?

**Stubbing is useful because we can bypass the real API altogether in development**. Let's extend the test with **cy.contains**:

```javascript
describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");
    
    // omitted for brevity

    cy.server();
    cy.route({
      url: "/users/**",
      method: "POST",
      response: { status: "Form saved!", code: 201 }
    });

    cy.get("form").submit();

    cy.contains("Form saved!");
  });
});
```

The test is **expected to fail** because there is no logic in place for sending the form to an API. In the next section we'll make the test pass.

## Sending form data to an API

Bear with me in this section! At the time of writing [Cypress couldn't intercept Fetch requests](https://github.com/cypress-io/cypress/issues/95). We're going to use **XMLHttpRequest** instead. ([Check this out for an introduction](https://github.com/valentinogagliardi/Little-JavaScript-Book/blob/1.1.0/manuscript/chapter9.md)).

Open up **form.js** and implement the logic:

```javascript
const form = document.forms[0];

form.addEventListener("submit", event => {
  event.preventDefault();
  new FormData(form);
});

document.addEventListener("formdata", event => {
  const body = Object.fromEntries(event.formData.entries());
  const jsonBody = JSON.stringify(body);
  const request = new XMLHttpRequest();
  request.open("POST", "https://jsonplaceholder.typicode.com/users/");
  request.send(jsonBody);
});
```

In this snippet I'm using the [formdata event](https://www.valentinog.com/blog/formdata/). The event is dispatched when we call **new FormData**.

In the event listener we build an object with **fromEntries** (ECMAScript 2019). The **we send the data to an API**.

To make the test pass we also need to **get the response back from the API** and save it to the document. For doing so we can listen on the onload event of XMLHttpRequest:

```javascript
// omit
document.addEventListener("formdata", event => {
  const body = Object.fromEntries(event.formData.entries());
  const jsonBody = JSON.stringify(body);
  const request = new XMLHttpRequest();
  request.open("POST", "https://jsonplaceholder.typicode.com/users/");
  request.send(jsonBody);
  // get the response
  request.onload = function() {
    const jsonResponse = JSON.parse(this.response);
  };
});
```

Finally we can **dangerously (just for keeping things simple) save the response in the page** (please don't never ever do this in a serious codebase):

```javascript
// omit
  request.onload = function() {
    const jsonResponse = JSON.parse(this.response);
    document.body.innerHTML += `Response from the server: ${jsonResponse.status}`;
  };
```

And now it's time to see the test passing!

## Stubbing XHR requests with Cypress: a passing test

To recap here's the complete test in **cypress/integration/form.spec.js**:

```javascript
describe("Form test", () => {
  it("Can fill the form", () => {
    cy.visit("/");
    cy.get("form");

    cy.get('input[name="name"]')
      .type("Molly")
      .should("have.value", "Molly");

    cy.get('input[name="email"]')
      .type("molly@dev.dev")
      .should("have.value", "molly@dev.dev");

    cy.get("textarea")
      .type("Mind you if I ask some silly question?")
      .should("have.value", "Mind you if I ask some silly question?");

    cy.server();
    cy.route({
      url: "/users/**",
      method: "POST",
      response: { status: "Form saved!", code: 201 }
    });

    cy.get("form").submit();

    cy.contains("Form saved!");
  });
});
```

And here's the complete code for **form.js**:

```javascript
const form = document.forms[0];

form.addEventListener("submit", event => {
  event.preventDefault();
  new FormData(form);
});

document.addEventListener("formdata", event => {
  const body = Object.fromEntries(event.formData.entries());
  const jsonBody = JSON.stringify(body);
  const request = new XMLHttpRequest();
  request.open("POST", "https://jsonplaceholder.typicode.com/users/");
  request.send(jsonBody);
  // get the response
  request.onload = function() {
    const jsonResponse = JSON.parse(this.response);
    document.body.innerHTML += `Response from the server: ${jsonResponse.status}`;
  };
});
```

One thing to keep in mind is that the **real API would not likely return the same shape of our fake stub**. When developing a real app you do need to adapt your tests to the real system.

But for now we're good and if you kept Cypress opened you should already see the test passing:

![Stubbing XHR requests](https://thepracticaldev.s3.amazonaws.com/i/kx50mguo37jgou2em1bx.png)

You can see a routes section at the top left and **XHR stub** in the test output, **sign that Cypress has intercepted the POST request**.

That's one of the best features of Cypress, not counting the dozens of commands and assertions ready to use.

With stubbing we can conclude the tutorial. Great job!

## Cypress tutorial for beginners: conclusions

I hope you learned something new with this tutorial and you'll apply these concepts to your next project! Testing is important!

End to end testing shouldn't be hard: Cypress makes it pleasing and enjoyable. The team at Cypress really nailed it.

Plus, the documentation is pure gold: [Cypress Docs](https://docs.cypress.io/guides/overview/why-cypress.html#In-a-nutshell) are filled up with best practices and examples.

Thanks for reading!

Originally published on [my blog](https://www.valentinog.com/blog/cypress/)

## Resources

Stefano Magni has a lot of nice testing tips in his [UI testing best practices](https://github.com/NoriSte/ui-testing-best-practices).