DEV Community

Cover image for Test Driven Development using Cypress
Amit Kumar Das
Amit Kumar Das

Posted on

Test Driven Development using Cypress

In this blog, we will be discussing test-driven development with Cypress along with some of the best practices for unit testing. As frontend developers, our main objective is to create different UI screens that include the development of various functional and reusable components. But one thing that most of us do not put into practice, maybe because we don’t understand its importance is testing, especially front-end unit testing.

What is Unit Testing?
First things first! Let’s understand what unit testing is.
Unit testing makes sure that the basic building blocks of the project including methods, UI elements, etc. are working properly. In React components, it lets the developer know if they are being rendered properly, whether the props are being passed to the components in a proper state, etc.

Why is it important?

  • In a bigger project, as the number of components increases, unit testing becomes the savior for the developers. We can test individual code before the complete integration and get faster feedback on the working of components without impacting the other sections of the project!
  • Inculcating the practice of writing unit tests, not only makes us think harder about the problem but also helps us discover the edges cases which in turn makes us write better quality of code.
  • Since the bugs are found in the starting phase, it reduces the overhead of fixing it in later stages, which would be a daunting task for us as developers.

So, we’ll be talking about the Cypress testing we do in Litmus.
Litmus is an open-source chaos engineering framework that provides tools to set up chaos in Kubernetes in order to help developers and SREs discover weaknesses in the application deployment. It is very easy to use and comes with detailed documentation. It also provides a large collection of chaos experiments, which you can find here. If you want to get started with Litmus, this article is surely going to help you. You can also join our slack community for an open discussion. Currently, we are developing the Litmus Portal which provides console and UI experience for managing, monitoring, and events around chaos workflows using the React and Typescript for the frontend.

What is Cypress?
Cypress is a Javascript-based testing framework that is built on top of Mocha which itself is a full-featured Javascript testing framework. It also includes cross-browser testing that makes it more preferable to use.
As mentioned in the official documentation of Cypress, Cypress facilitates a developer to write all types of tests :

  • Unit tests
  • Integration tests
  • End to End tests

Why Cypress?
Before jumping into its implementation let’s get to know why did we choose Cypress for unit testing. So the primary reasons are:

  • With in-browser execution, it is incredibly fast!
  • We didn’t have to worry about its setup as it is very easy to use and provides very good and detailed documentation.
  • It also boasts about a growing community.

Some of the best practices that we have followed while doing unit testing are:

  • Used unique “data-cy” attributes to make it easier in targeting the elements, targeting the elements using a tag, id or class should be prevented as these are highly subject to change. For example:
<button id="main" class="btn btn-large" name="submission"
  role="button" data-cy="submit">Submit</button>
  cy.get('[data-cy=submit]').click()
Enter fullscreen mode Exit fullscreen mode
  • Maintained well defined and clean folder structure. All the tests are stored in cypress/components in the litmus-portal/frontend directory.
  • We have added logs wherever it is required to make the debugging easy.
  • We have tried to limit one assert per method to avoid confusion in case of failure.
  • Each of the tests is independent of each other so that the tests can be executed in any order and failure of one test case doesn’t affect others.
  • We have kept each of the tests short and simple to enhance readability and understanding.

Since the project is using Typescript in the frontend, we have written all the tests in the same.
So, now we’ll be setting up cypress along with a small react app.

Prerequisites:

  • React app
  • Custom components
  • Cypress

Installing cypress

  • Install cypress using npm
  npm install cypress
Enter fullscreen mode Exit fullscreen mode
  • Install cypress using yarn
  yarn add cypress 
Enter fullscreen mode Exit fullscreen mode

Once Cypress is successfully installed, you can try it out by running these commands.

Open Cypress

  • Open cypress using npm
  npx cypress open
Enter fullscreen mode Exit fullscreen mode
  • Open cypress using yarn
  yarn run cypress open 
Enter fullscreen mode Exit fullscreen mode

Setting up Cypress in the react app with Typescript for the unit-test:

Install Cypress React Unit Test Plugin

npm install --save-dev cypress cypress-react-unit-test
Enter fullscreen mode Exit fullscreen mode

Configure your tsconfig.json by adding these

{
   "compilerOptions": {
       "target": "es5",
       "lib": ["dom", "dom.iterable", "esnext"],
       "types": ["node", "cypress"]
   },
   "include": ["src", "cypress/component/*.tsx"]
}
Enter fullscreen mode Exit fullscreen mode

Configure your cypress.json by adding these

{
   "experimentalComponentTesting": true,
   "componentFolder": "cypress/component",
   "specFiles": "*spec.*",
   "defaultCommandTimeout": 4000,
   "execTimeout": 200000,
   "taskTimeout": 200000
}
Enter fullscreen mode Exit fullscreen mode

Make sure you specify the folder that contains the test cases, here the test scripts are present in the cypress/components folder.
These timeouts can be checked here.

Inside your Cypress folder add the following export statements inside plugin/index.js file

* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
   require("cypress-react-unit-test/plugins/react-scripts")(on, config);
   // IMPORTANT to return the config object
   // with the any changed environment variables
   return config;
};
Enter fullscreen mode Exit fullscreen mode

In this file, we have exported a function. Cypress is going to call this function, pass the project’s configuration, and enable it to bind to the events exposed.

Writing your first unit test in Cypress

So we have created this basic react app in which the user will enter a value between 1-10 using an InputField and the progress bar will change its color accordingly, it also has a semi-circular progress bar that takes input and changes according to the value entered. We have used these components in the Litmus Portal as well.

Use Case :

  • If the entered value is between 1-3, the progress bar color will change to red.
  • If the entered value is between 4-6, the progress bar color will change to yellow.
  • If the entered value is between 7-10, the progress bar color will change to green.
  • In case of the semicircular progress bar, it will take an input between 1-100 and display the result accordingly.

The landing page of our react app looks like this.

Home Page

Let’s write some unit tests for these components:

Linear ProgressBar
For this component we’ll perform few tests, for example :

  • The length of the stroke according to the value
  • The color progress bar when the value is 2
  • The color progress bar when the value is 6
  • The color progress bar when the value is 8

The test script for this component looks like this:

/// <reference types="Cypress" />
import React from "react";
import { mount } from "cypress-react-unit-test";
import LinearProgressBar from "../../src/components/ProgressBar/LinearProgressBar";

describe("Linear Progressbar Testing", () => {
 it("Progressbar stroke for value 2", () => {
   mount(<LinearProgressBar value={2} />);
   cy.get(".rc-progress-line-path").should(
     "have.css",
     "stroke-dasharray",
     "20px, 100px"
   );
 });
 it("Progressbar stroke for value 8", () => {
   mount(<LinearProgressBar value={8} />);
   cy.get(".rc-progress-line-path").should(
     "have.css",
     "stroke-dasharray",
     "80px, 100px"
   );
   cy.get(".rc-progress-line-path").should("have.attr", "stroke", "#109B67");
 });
 it("Progressbar stroke for value 6", () => {
   mount(<LinearProgressBar value={6} />);
   cy.get(".rc-progress-line-path").should(
     "have.css",
     "stroke-dasharray",
     "60px, 100px"
   );
   cy.get(".rc-progress-line-path").should("have.attr", "stroke", "#F6B92B");
 });
Enter fullscreen mode Exit fullscreen mode

describe(): This function is used to describe the test suite for the corresponding component as “Linear Progressbar Testing”
it(): Inside that, we have specified the particular test names like “Progressbar stroke for value 2” using this function.

Note: describe() and it() are some of the test interfaces provided by Mocha.

describe("Linear Progressbar Testing", () => {
 it("Progressbar stroke for value 2", () => {
   mount(<LinearProgressBar value={2} />);
   cy.get(".rc-progress-line-path").should(
     "have.css",
     "stroke-dasharray",
     "20px, 100px"
   );
 });
Enter fullscreen mode Exit fullscreen mode

mount(): This function mounts the component for testing.
We have added the props that the components take i.e value={2} here.
Once the component has been mounted/rendered properly we can check different properties of the component, like in this case, we are checking for the width of the progress bar when the value is 2 with the help of should(“have.css”, “css-property”, “css-value”)

Once the test succeeds , we get the following results in the browser:
Cypress Test 1 Linear Progressbar

Similarly, in the next test:

mount(<LinearProgressBar value={8} />);
   cy.get(".rc-progress-line-path").should(
     "have.css",
     "stroke-dasharray",
     "80px, 100px"
   );
   cy.get(".rc-progress-line-path").should("have.attr", "stroke", "#109B67");
 });
Enter fullscreen mode Exit fullscreen mode

Here we are re-mounting the component with a different prop ie value as 8.
Once it is done, we can check the width of the progress bar and can check if the correct color is being displayed, in this case “#109B67” as the value is above 7.
This can be done using should(“have.attr”,” stroke”,”#109B67”), here we are checking if the color of stroke is according to the use-case i.e Green / #109B67.

Cypress Test 2 Linear Progress Bar

Semi-Circular ProgressBar
The test script for this component looks like this:

/// <reference types="Cypress" />

import React from "react";
import { mount } from "cypress-react-unit-test";
import SemiCircularProgressBar from "../../src/components/ProgressBar/SemiCircularProgressBar";

// Test Suite -
// Progress Bar props -> value = 50, 10, 100
describe("Semi Circular Progress Bar has appropriate values", () => {
 [50, 10, 100].map((i) => {
   it(`Value is equal to ${i}`, () => {
     const wrapper = <SemiCircularProgressBar value={i} />;
     mount(wrapper);
     cy.get("[data-cy=progressValue]").then((value) => {
       expect(value[0].innerText).to.equal(`${i}%`);
     });
   });
 });
});

// Test Suite - Icon has the correct src
describe("Icons have a correct path", () => {
 it("Progress Bar icon has a correct source", () => {
   const wrapper = <SemiCircularProgressBar value={40} />;
   mount(wrapper);
   cy.get("[data-cy=progressIcon]")
     .should("have.attr", "src")
     .should("include", "./icons/graph.svg");
 });
});
Enter fullscreen mode Exit fullscreen mode

For this component we are performing four tests. Three tests to check if the proper values are being passed in the props and the last test is to check if the image is taken from the correct source.

Here we are using an array of values that is being mapped with the component.

[50, 10, 100].map((i) => {
   it(`Value is equal to ${i}`, () => {
     const wrapper = <SemiCircularProgressBar value={i} />;
     mount(wrapper);
     cy.get("[data-cy=progressValue]").then((value) => {
       expect(value[0].innerText).to.equal(`${i}%`);
     });
   });
 });

Enter fullscreen mode Exit fullscreen mode

Here we are using "[data-cy=progressValue]" to easily target the element for which we are writing the test cases. We can then make sure that the correct value is being displayed in the component as shown below.

Test 1 Semi Circular Progress Bar

Test 2 Semi Circular Progress Bar

it("Progress Bar icon has a correct source", () => {
   const wrapper = <SemiCircularProgressBar value={40} />;
   mount(wrapper);
   cy.get("[data-cy=progressIcon]")
     .should("have.attr", "src")
     .should("include", "./icons/graph.svg");
 });

Enter fullscreen mode Exit fullscreen mode

Similarly, in these test cases, we are checking if the source of the image is correct with the should() attribute that we discussed above.

Conclusion

So these were some of the basic examples of unit testing we did with Cypress. If you wish, you can also add more test suites here. Feel free to check out our ongoing project - Litmus Portal and do let us know if you have any suggestions or feedback regarding the same. You can always submit a PR if you find any required changes.

This blog is also contributed by:

  1. Saranya Jena
  2. Vedant Shrotria

Since this is our first blog, would love to know your views and suggestions. Feel free to reach out to us if you have any queries. Hope you found it insightful!

Last but not the least, if chaos engineering is something that excites you or if you want know more about cloud native chaos engineering, don’t forget to checkout our Litmus website and the Litmus repo. Do leave a star if you find it interesting. 😊

Cheers!

Top comments (0)