Unit testing with React and Cypress
Tags: react, webdev
Published: 2022-09-14 19:02:24

**Written by [Ishan Manandhar](https://blog.logrocket.com/author/ishan-manandhar/)✏️**

Every developer wants to ship a bug-free production application. To achieve this, we need to consider how we can integrate tests into our apps. There are many testing tools, frameworks, and types of tests available to us. 

[Cypress](https://docs.cypress.io/) is a modern, automated testing suite. It's a fully open source testing framework based on JavaScript and is built with libraries like [Mocha](https://docs.cypress.io/guides/references/bundled-libraries#Mocha) and [Chai](https://docs.cypress.io/guides/references/bundled-libraries#Chai) that support BDD and TDD assertion styles. Further, using Cypress will be easy if you’re familiar with writing tests in JavaScript. 

Cypress covers two major styles of writing tests: end-to-end testing and unit testing. In this article, we’ll be going over unit testing with Cypress. We’ll overview unit testing, discuss the benefits of using Cypress, Cypress' installation, commands, and assertions, run Cypress, and write our tests.

*   [What is unit testing?](#what-unit-testing)
*   [Benefits of testing with Cypress](#benefits-testing-cypress)
*   [Installing Cypress](#installing-cypress)
*   [Commands and assertions in Cypress](#commands-assertions-cypress)
*   [Running Cypress in our application](#running-cypress-application)
*   [Setting up our sample component](#setting-up-sample-component)
*   [Writing unit tests in Cypress](#writing-unit-tests-cypress)

## What is unit testing? <a name="what-unit-testing">

Unit testing is used by developers to verify that an application runs well. It involves checking every module of the application independently. 

The term “unit” refers to a single testable part of an application. This process helps testers and developers catch and understand early bugs in a development environment. The staff then has enough time to change any defect code. 

It’s not uncommon to see confusion between the different types of testing. Below is a table covering the main differences between end-to-end and unit testing:

<table>

<tbody>

<tr>

<td>End-to-end (E2E) testing</td>

<td>Unit testing</td>

</tr>

<tr>

<td>Used to test the entire application from start to end.</td>

<td>Used to test the individual units of the application.</td>

</tr>

<tr>

<td>Checks the behavioral flow of the application.</td>

<td>Checks functional verification of the unit.</td>

</tr>

<tr>

<td>Generally a costly means of testing.</td>

<td>Generally a cost-efficient means of testing.</td>

</tr>

<tr>

<td>Mostly performed manually.</td>

<td>Mostly automated.</td>

</tr>

<tr>

<td>Tests end-to-end parts of the application.</td>

<td>Modules are tested separately.</td>

</tr>

</tbody>

</table>

Since Cypress covers both worlds of testing as mentioned earlier, it has been an excellent testing choice for the modern applications we build today. Let’s take a look at how Cypress helps us to build better testable products.

## Benefits of testing with Cypress <a name="benefits-testing-cypress">

In general, these are the biggest benefits of testing with Cypress that I’ve found so far:

*   Time travel debugging
*   Live code reloading
*   Current and previous state differentiation
*   The option between running headless tests or testing in a browser
*   Ability to take screenshots and video recordings of tests
*   Asynchronous testing
*   In-browser debugging environment

Cypress tests take snapshots and can provide readable errors and stack tracing for easier debugging. We also don't need to add sleep timeout events to our test, as it has an automatic waiting functionality that ensures tests wait for commands and assertions before moving forward. 

I’ve found that Cypress tests are less flaky and much faster than traditional testing suites. Cypress supports parallel execution, imports external libs, manipulates objects, stubs API calls and network traffic control, and [has awesome learning documentation](https://docs.cypress.io/guides/overview/why-cypress#Setting-up-tests). 

Moreover, Cypress can test every layer of our application, including the UI, database, API, frontend stores, cross-browser functions, XHR requests, and more.

## Installing Cypress <a name="installing-cypress">

Cypress runs on a Node.js server that frequently communicates with the browser (a test runner), instrumentalized by Cypress. It concurrently runs two iFrames, one of which is our test code. The other is an iFrame that looks at the tests in action. 

We’ll be creating a React and Vite project from scratch with the command:

```bash
npm create vite@latest
```

Inside our application, let’s add Cypress to our dev dependency by running the following command:

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

Please make sure you choose the option for the unit testing available to us. With this in place, we will generate a couple of folders:

```javascript
├── cypress
│   ├── downloads
│   └── component
│   └── fixtures
│   └── support
├── cypress.config.js
```

`downloads` will come in handy when we want to use screenshots or video recordings of our test being executed. 

`component` is the location where we’ll be writing our unit test. We’ll create new files that can be selected later in the iFrame. 

`fixtures` will be loading up all the JSON data we need to test. 

`support` is where we can add our custom commands. We can import them later and use them in our test. 

`cypress.config.js` is where we can dynamically modify configuration values and environment variables from our Cypress configuration. 

Cypress automatically closes the browser once the test is executed. We don't explicitly need to close the browser window. 

A full, working code repository can be found on my GitHub link [here](https://github.com/ishan-is-me/react_cypress_unit_testing).

## Commands and assertions in Cypress <a name="commands-assertions-cypress">

Commands and assertions are important concepts in Cypress, so let’s go over them before we continue. 

First, we have commands. Cypress provides an API and methods to interact with the UI of the application. The commands available to us will have an inbuilt method that we can invoke in our test blocks. Essentially, we can simulate a user trying to perform an operation that is used to create an assertion. 

In the example snippet below, we use a `get` command to get an element. We then use `should` and a chained assertion passed on to assert something we expect from the test.

```javascript
it('sample testing ', () => {
 cy.visit('/')
 cy.get('#name').type('Elon Musk')
 cy.get('#password').type('programming-rocks')
 cy.get('#submit').click()
 cy.get('.messages--error').should('be.visible').and('contain', 'Unrecognized username or password. Forgot your password?')
})
```

Assertions are the checkpoints of our test block that confirm if an automated test has passed or failed. Cypress bundles the Chai, [jQuery](https://jquery.com/), and [Sinon.JS](https://sinonjs.org/) libraries for assertions. They check the desired, expected application in which the test is running. A complete listing of assertions can be found in the [documentation here](https://docs.cypress.io/guides/references/assertions).

## Running Cypress in our application <a name="running-cypress-application">

It would be really handy if we could open Cypress from the command-line interface (CLI). And we can! To do this, we can add:

```javascript
 "scripts": {
   "cypress": "cypress open"
 }
```

This’ll allow us to open a default Cypress iFrame where we can select the test files to execute. We can run this with the command `npm run cypress.`

## Setting up our sample component <a name="setting-up-sample-component">

Now, we’ll begin testing a simple movie listing component we created. It takes in the name of a movie and adds it to our UI when we click a button. We also can click on the movie listing and mark it as seen, as well as dismiss the all-seen movies.

```javascript
import { useState } from 'react';

function Movielist() {
 const [userInput, setUserInput] = useState('');
 const [movieList, setMovieList] = useState('');

 const handleChange = (e) => {...}

 const handleSubmit = (e) => {...}

 const handleClick = (e) => {...}

 const handleSeen = (e) => {...}

 return (
   <div className="wrapper">
     <form onSubmit={handleSubmit}>
       <input value={userInput} type="text" onChange={handleChange} placeholder="Movie wishlist" />
       <button>+Add movies</button>
     </form>
     <div className="movieList">
       {!movieList && <span data-cy='empty' className="empty">No movies here</span>}
       <ul data-cy='movie-list'>
         {movieList && movieList.map(m => {
           return (
             <li onClick={handleClick} className={m.seen ? "strike movie-list" : "movie-list"} id={m.id} key={m.id}>
               <span>
                 {m.movie}
			'X'
               </span>
             </li>
           )
         })}
       </ul>
     </div>
     {movieList.length > 0 && <><button data-cy='clear-movie' className="outline-btn" onClick={handleSeen}>Clear seen movies</button></>}
   </div>
 )
}

export default Movielist
```

With this component in place, we can start testing this component in isolation.

## Writing unit tests in Cypress <a name="writing-unit-tests-cypress">

Inside our `component` folder, we’ll create a new file called `Movielist.cy.js`. This will contain a simple test execution that visits the application we are running locally.

```javascript
import Movielist from '../../src/components/Movielist';

describe('<Movielist>', () => {
 it('Mount the component', () => {
   cy.mount(<Movielist />);
 })
})
```

The above `import` statement is supported and we also can mount the functions for every supported framework from the Cypress package. If we run the command `npm run cypress`, we should see our component ready to test in a browser.

```javascript
describe('<Movielist>', () => {
 it('Component mounts', () => {
   cy.mount(<Movielist />);
 })
 it('Component mounts', () => {
   cy.mount(<Movielist />);
   cy.get('[data-cy=empty]').contains('No movies here');
 })
})
```

We need to mount our component in each `it()` block we create for our test. To do so, we can make use of the `beforeEach()` method provided to us. This’ll look cleaner and easier altogether:

```javascript
describe('<Movielist>', () => {
 beforeEach(() => {
   cy.mount(<Movielist />);
 });
 it('Check no movies', () => {
   cy.get('[data-cy=empty]').contains('No movies here');
 })
})
```

This definitely looks better than before. Now, we’ll move ahead and add a test to check whether or not we can make the movie wishlist work.

```javascript
describe('<Movielist>', () => {
 beforeEach(() => {
   cy.mount(<Movielist />);
 });

 it('The List of movies appends', () => {
   cy.get('[data-cy=empty]').contains('No movies here');
   const formInput = cy.get('form input');
   formInput.should('have.value', '');
   formInput.type('Monster Inc.')
     .should('have.value', 'Monster Inc.');
   cy.get('form button').click();
   formInput.clear();
   formInput.type('Circle of eight')
     .should('have.value', 'Circle of eight');
   cy.get('form button').click();
   cy.get('[data-cy=movie-list]').children().should('have.length', 2);
 })
 })
```

Up until this point, we have passed all the tests we’ve written and checked that we’ve achieved the behavior we expected from the components.

We’ll now test the last piece of functionality attached to this component that dismisses the seen movies with a **Clear movie** button. This sounds similar to something we have written before, let's see what it looks like:

```javascript
 it('uncheck movie', () => {
   const lastListitem = cy.get('[data-cy=movie-list]:nth-child(1) li:last-child');
   lastListitem.click();
   lastListitem.should('have.class', 'strike');
   cy.get('[data-cy=clear-movie]').click();
   cy.get('[data-cy=movie-list]').children().should('have.length', 1);
   cy.get('[data-cy=clear-movie]').click();
   cy.get('[data-cy=movie-list]').children().should('have.length', 1);
 })
```

We checked whether we can mark the movie as seen, which then adds a strike through the movie name. We cleared out the seen movies and tested the number of movies available to us in the interface. We’ve also tested the component we’ve written. 

It's always better to have ample code test coverage. You may have noticed that it looks just like a real click interaction and simulation in the Cypress UI test. This is also one of the major benefits of using Cypress: visible interfaces, time traveling, and many others. 

Testing our components of the application is much faster [than doing end-to-end testing](https://blog.logrocket.com/real-confidence-with-cypress-e2e-tests/). Our component tests perform similarly to component tests executed within Node-based runners, like Jest and Mocha.

## Conclusion

And there we have it! We have successfully written a unit test with Cypress for a single component of our app. We can now ship fully working, bug-free code to our users. 

Cypress is language agnostic so you can use it with any other programming language, not just JavaScript. With this, we have implemented fully automated testing that addresses the pain points of traditional testing tools. Cypress is a great choice for frontend developers and test automation engineers to write automated web tests. 

You can learn more about Cypress in their [official docs](https://docs.cypress.io/guides/overview/why-cypress). They’re an excellent resource to learn more about Cypress and ways to use it.

---

##[LogRocket](https://www2.logrocket.com/react-performance-monitoring) proactively surfaces and diagnoses the most important issues in your React apps

[![React Performance Monitoring](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://lp.logrocket.com/blg/signup)

Thousands of engineering and product teams use [LogRocket](https://www2.logrocket.com/react-performance-monitoring) to reduce the time it takes to understand the root cause of technical and usability issues in their React apps. With LogRocket, you'll spend less time on back-and-forth conversations with customers and remove the endless troubleshooting process. LogRocket allows you to spend more time building new things and less time fixing bugs.

Proactively fix your React apps — try [LogRocket](https://www2.logrocket.com/react-performance-monitoring) today.