Cypress testing

Automated testing using the Cypress testing library

Francisco DiasFrancisco Dias
about 1 month ago | 7 minute read

Cypress testing

This is Part 2 of a series of articles I’m writing on automated testing, (specifically React testing), and the value the JDLT team get from it.

In Part 1, React component testing with Jest and React Testing Library, I explored how I use automated tests as a way of adding value in my first role as a developer in a company with a large code-base to learn.

Back then, I focussed on Unit testing and Integration testing while using Jest and React Testing Library. This time I’ll be writing about End-to-End testing and how we use it to give us confidence our apps work as intended.

So, what are Unit, Integration and End-to-End tests? Well, most automated tests fall into one of three types:

Type of TestWhat does it test? It tests …Popular tools
UnitSmall independent parts work as expected. Isolated function/classes/algorithm. E.g. Shared functions usually found in the utils folder.Jest and React Testing Library / Enzyme
IntegrationSeveral units/parts work together as expected. E.g. Two React components where one is the parent while passing props to its child.Jest and React Testing Library / Enzyme
End-to-EndBehaves as the user would, clicking/typing around the app. Runs an entire app in the browser and interacts with both front and back end as a user would. Like a robot user!Cypress or Puppeteer and Jest

I'll assume you have a basic knowledge of TypeScript and React. I use a MacBook for development so I'll be using Zsh & Brew and I also use Yarn rather than NPM. I will be using a React project I worked on previously to show some examples.

Cypress and Jest & Puppeteer seem to get a considerable amount of attention for their accessibility and cost effectiveness when it comes to End-to-End testing. I will therefore produce two articles exploring my experience using these tools with a React app.

This article will focus on Cypress and Part 3 will focus on Jest and Puppeteer.

What is Cypress?

Cypress is a tool developed by a company of the same name. The test runner is open source and free although they offer paid services that integrate with it.

Why Cypress?

Cypress has been designed to be the End-to-End testing tool of choice and as result it is very reliable and makes testing enjoyable for the developer. It supports testing in different browsers, and it is very easy to get started because there are no servers, drivers, or dependencies to install or configure. Cypress also offers out of the box examples of tests and video recordings of each session which you can leverage.

Cypress installation

Install Cypress:

$ npm install cypress --save-dev

Or

$ yarn add -D cypress

Adding Cypress commands to scripts in package.json:

  ...
  "scripts": {
    "cypress-open": "cypress open",
    "cypress-run": "cypress run --browser chrome"
  }
  ...

cypress run –browser chrome will default to chrome as a browser when running tests.

Cypress-open will open the cypress console window while Cypress-run just runs tests and when all the tests have run, it shuts down cypress and the browser.

While configuring cypress bear in mind to include baseUrl to tsconfig.json and make the relevant changes to cypress.json should you decide to move the cypress folder from root or define options for when tests are ran (check Cypress configuration docs for more information).

Example of cypress.json should you decide to move the cypress folder to a folder called tests which lives in root folder of the project:

{
  "cypressFolder": "test/cypress",
  "fixturesFolder": "test/cypress/fixtures",
  "integrationFolder": "test/cypress/integration",
  "pluginsFile": "test/cypress/plugins/index.js",
  "screenshotsFolder": "test/cypress/screenshots",
  "videosFolder": "test/cypress/videos",
  "supportFile": "test/cypress/support/index.js"
}

To ensure your tests don’t break inadvertently, use of the tag data-cy as selector identifiers instead of implementation details such as CSS classes or DOM location. Doing so will improve accuracy in targeting the correct element and it will make your tests more resilient to future changes to the component’s implementation.

Running tests

What to test in a simple To Do list app?

When thinking of your End to End tests be clear of the user functions of what you’re testing (e.g. login into an user area) and build conditions (e.g. invalid vs valid username and password).

Imagine we have an app to help us manage our To DO list and that we want to test it. Our app is very simple therefore our tests will cover:

  1. Render without crashing
  2. Add a new ‘to do’ item to the list
  3. Delete an existing ‘to do’ item

For each scenario there will be a series of different test because the build conditions may change, for example what if the input field is empty or if the name is the same as an existing ‘to do’ item.

Render without crashing:

User functions:

  • n/a

Build conditions:

  • Render without crashing both with and without connection to server,
  • Show alert message when rendering without connection to the server,
  • With connection to server render two default To Do items,
  • Render an input field for typing up new To Do items,
  • Render an ‘Add’ button for adding To Do items.

Add a new ‘to do’ item to the list:

User functions:

  • User can fill the input field,
  • User click’s button add to add new ‘to do’ item,

Build conditions:

  • If the ‘Add’ button is pressed but the input field is empty, prevent a new To Do item from being created,
  • If the ‘Add’ button is pressed but the input field is empty, show an alert to the user,
  • If the ‘Add’ button is pressed and the input field has content, add a new To Do item,
  • Data being passed down fromTo Do list to To Do Item as props, each To Do Item should render the text that was passed down to it.

Delete an existing ‘to do’ item:

User functions:

  • When the ‘Delete’ button is pressed for a singleTo Do item, remove that To Do item from the App.

Build conditions:

  • If the first To Do item has been removed from the app, the second item should now become the first (and only) item.

How to test with Cypress?

Cypress will create many examples of tests within the integration folder, use it to get more familiar with the syntax and how to structure your tests – delete these when and if not needed. I will therefore add a very simple and straight forward example of a Cypress test. I also recommend you use Cypress docs to get familiar with the extensive number of assertions, commands and utilities.

The naming convention for testing files is to end it on *_spec.ts, as per our test below within the file my-test_spec.ts. The tests should be placed in the integration folder.

describe("<App/>", () => {
  before(() => cy.visit("http://localhost:3000/"))

  it("Renders without crashing", () => {
    cy.get("span").contains("My Todo List")
  })

  describe("The default Ui", () => {
    it("Renders two default todo items", () => {
      cy.get(".row").should("have.length", 2)
    })

    it("Has an input field", () => {
      cy.get("input").should("have.length", 1)
    })

    it("Has an Add button", () => {
      cy.get('[data-cy="addButton').should("have.text", "Add")
    })
  })
})
;<button
  data-cy="addButton"
  type="submit"
  className="btn btn-primary mb-2 col-4 col-sm-2"
>
  Add
</button>

All tests use describe() blocks and it() functions, a similarity you will notice to other tests you may have used in the past. The pattern to follow would be a describe() block with it() statements inside it.

The it() function takes a string which describes what is being tested and a call-back function. The call-back will in general check/find something (cy.get('[data-cy="addButton') the add button in this case and then check an assertion against it (.should("have.text", "Add").

Cypress was built with flaky tests (pass or fails periodically without any code changes) in mind but don’t forget to change assertion’s condition to make tests fail as well as pass.

Summary

End-to-End testing should focus on replicating user behaviour and interact with both the front and back ends as a user would do. Broad tests which cover user function and build conditions are better than implementation details as these are less likely to break.

Further reading

JDLT

At JDLT, we create custom business software for SMEs and large organisations.

Please use our contact us page or get in touch at hi@jdlt.co.uk if you'd like to talk about how we can help.

Share me!