Basic React Component Tests with Cypress

Mohit Mutha
6 min readJan 29, 2023

--

Photo by Ferenc Almasi on Unsplash

This article guides through setting up Cypress BDD for E2E and Component tests. The later articles in this series will cover end-to-end tests with Cypress and BDD with Cypress followed by test result reporting and measuring coverage.

Let us start with a basic react application. I have created a application with the below behavior

  • User can input a number in a text box
  • The display blocks display the square, cube and the Fibonacci value of the number index
  • User can mark a number as favorite
  • The list of favorites is displayed and the user can do a quick select of the favorite number
  • User can add and remove favorites

To follow the steps in the article do the following

Clone the repo and checkout the first tag which has the application but none of the tests or Cypress setup

git clone https://github.com/mohitmutha/cypress-demo-app.git
git checkout -b mybranch INITIAL_APP

Now lets add cypress as a dev dependency

yarn add -D cypress

Once done we can start Cypress and setup our component tests

yarn cypress open

Click on Component Testing to start configuring the project

For now we will not go into the configuration or structure created but suffice to say that a folder cypress is created at the root of the project with some directories. We will go into the config in a later post.

Now lets go back to our project in VS Code or other IDE and start creating our tests. The sample application has the below components

  • Input Card — Accepts the number input, creation of favorites and expand collapse to show or hide content
  • Favorites Card — Shows favorites and allows user to select a favorite
  • Square Result Card — Displays square value of input number
  • Cube Result Card — Displays cube value of input number
  • Fibonacci Result Card — Displays the Fibonacci series number at given index

Let us pick the Square Result Card to write a few unit tests like

  • A valid number say 2 is passed then the corresponding result is computed and displayed`
  • A non-numeric value is passed then an error message is displayed

Create a new spec file under cypress/component/SquareCard.cy.js

Now let us implement the tests we described above

Before starting the implementation let us refactor our code a little so that we can locate the elements easily using selectors.

We will add id property to our elements to make it easy for our tests to locate the elements. A better option is to add meta element like data-testid but for the sake of simplicity lets do it with a simple id for now.

//ResultDispayCard.js
import { Card, CardContent, CardHeader, Container, Typography } from "@mui/material"

const ResultDisplayCard = (props) => {
return <Container><Card id={`${props.id}-card`}>
<CardHeader
id={`${props.id}-card-title`}
title={props.title}
subheader={props.subTitle}
/>

<CardContent id={`${props.id}-card-content`}><Typography>
<h1 id={`${props.id}-value`}>{props.displayText}</h1>
</Typography></CardContent></Card></Container>
}

export default ResultDisplayCard
//SquareCard.js
import { useEffect, useState } from "react"
import ResultDisplayCard from "./ResultDisplayCard"

const SquareCard = (props) => {
const [result, setResult] = useState()
useEffect(() => setResult(Math.pow(props.value, 2)),[props.value])
return <ResultDisplayCard id='square-result' title="Square" displayText={result}/>
}

export default SquareCard

Now let us write a test to verify if the Square Card displays the heading Square

Open the file “cypress/component/SquareCard.cy.js” and add below to it

import SquareCard from "../../src/components/SquareCard"

describe('Square card component', () => {
it('has card heading Square', () => {
cy.mount(<SquareCard>Click me!</SquareCard>)
cy.get('#square-result-card-title').should('contains.text', 'Square')
})
})

Let us walk through this

  • Cypress is built on top of Mocha and uses the same style of defining the tests. So these two lines describe the test
 describe('Square card component', () => {
it('has card heading Square', () => {
  • The next line mounts the component we want to test
cy.mount(<SquareCard>Click me!</SquareCard>)
  • The line below locates the element we want to check using the id we assigned and makes an assertion on the content of the element. Cypress uses the Sinon-Chai library for assertions
cy.get('#square-result-card-title').should('contains.text', 'Square')

Great lets run this test now

We can see the test run and the rendering of the component to the right. Cypress

We can already see that our next test is going to fail :)

Lets write a test now to check if a error message is rendered when an invalid number is passed to the component

Add the below test to the test spec

it('renders an error when an undefined value is passed', () => {
cy.mount(<SquareCard>Click me!</SquareCard>)
cy.get('#square-result-value').should('contains.text', 'Error - Non numeric value')
})

it('renders an error when a non-numeric value is passed', () => {
cy.mount(<SquareCard>Click me!</SquareCard>)
cy.get('#square-result-value').should('contains.text', 'Error - Non numeric value')
})

Now run the tests. These should fail since we have not checked for undefined or NaN in our component

Let us change our code so that the tests pass

//SquareCard.js
import { useEffect, useState } from "react"
import ResultDisplayCard from "./ResultDisplayCard"

const SquareCard = (props) => {
const [result, setResult] = useState()
useEffect(() => {
if (isNaN(props.value)) { // <- Changed lines Start
setResult("Error - Non numeric value")
} else {
setResult(Math.pow(props.value, 2))
}<- Changed lines End

}, [props.value])
return <ResultDisplayCard id='square-result' title="Square" displayText={result} />
}

export default SquareCard

Once you save the file the and if the Cypress console is open then the tests should re-run automatically and you will see the result as below

Now we will add a test case which tests a success scenario where a number is passed as input and the result is asserted. Add below to the test file

it('renders the square of the value passed', () => {
cy.mount(<SquareCard value="2"></SquareCard>)
cy.get('#square-result-value').should('contains.text', '4')
})

The test should pass but this is a insufficient test as it only tests for one input i.e. 2. We should be checking for different kinds of input for e.g. Negative integers, decimals, negative decimals etc.

However writing a test for each input can be cumbersome. So let us parameterize our test so that the same test is executed for different inputs. Change the last added test as below

it('renders the square of the value passed', () => {
const inputData = Array.from([[2,4],[-3,9],[0.2,0.04],[-0.5,0.25]])
inputData.forEach(x => {
cy.mount(<SquareCard value={x[0]}></SquareCard>)
cy.get('#square-result-value').should('contains.text', x[1])
})
})

Here we can see that we have taken a two dimensional array with the input and result as items in the array

const inputData = Array.from([[2,4],[-3,9],[0.2,0.04],[-0.5,0.25]])

Then we are going through this array and parameterizing the input and the assertion

inputData.forEach(x => { 
cy.mount(<SquareCard value={x[0]}></SquareCard>) // <- Input from array
cy.get('#square-result-value').should('contains.text', x[1]) //<-Assertion from array
})

You should see the test result as below

We can now change the other display components and add tests for them. You will find these in the repo https://github.com/mohitmutha/cypress-demo-app

In the next part we will see how to run e2e tests and in the following part we will apply BDD.

--

--