DEV Community

Cover image for Testing Your React App with Cypress
Debajit Mallick
Debajit Mallick

Posted on

Testing Your React App with Cypress

Introduction

In this blog, I will walk you through setting up a bulletproof testing setup using React + Cypress. We'll cover everything from the basics to testing those tricky async API calls that always seem to break at the worst possible moment.

Getting Started

Firstly, we are going to create a React app using Vite. As CRA is deprecated, Vite is the standard way for creating a React project. Open your terminal and run the following commands:

npm create vite@latest my-react-app -- --template react-ts cd my-react-app npm install 
Enter fullscreen mode Exit fullscreen mode

To start the project on localhost:

npm run dev 
Enter fullscreen mode Exit fullscreen mode

Head over to http://localhost:5173 and you should see your React app.

Cypress Setup

Here's where things get interesting. Cypress isn't just another testing framework—it's like having a tester that clicks through your app exactly like a real user would.

Let's get Cypress installed:

npm install --save-dev cypress 
Enter fullscreen mode Exit fullscreen mode

Now run the following command:

npx cypress open 
Enter fullscreen mode Exit fullscreen mode

This will create a cypress/ folder and some initial files. Don't worry about all the folders that appear. It's all for the setup.

Cypress Configuration

Time for a little configuration. Create a cypress.config.ts file in your project root:

import { defineConfig } from 'cypress' export default defineConfig({ e2e: { baseUrl: 'http://localhost:5173', supportFile: 'cypress/support/e2e.ts', specPattern: 'cypress/e2e/**/*.cy.{js,ts,jsx,tsx}', }, }) 
Enter fullscreen mode Exit fullscreen mode

Here's a pro tip that'll save you some headaches: install start-server-and-test. This command will start your dev server and run tests automatically:

npm install --save-dev start-server-and-test 
Enter fullscreen mode Exit fullscreen mode

Update your package.json scripts:

"scripts": { "dev": "vite", "test:e2e": "start-server-and-test dev http://localhost:5173 'cypress open'" } 
Enter fullscreen mode Exit fullscreen mode

Now you can run npm run test:e2e and everything just works.

First Test

Let's write a test that actually does something. Create cypress/e2e/homepage.cy.ts:

describe('Homepage', () => { it('should load the homepage and display welcome text', () => { cy.visit('/') cy.contains('Vite + React').should('be.visible') }) }) 
Enter fullscreen mode Exit fullscreen mode

Run your tests with:

npm run test:e2e 
Enter fullscreen mode Exit fullscreen mode

If you see that test pass, congratulations! You just wrote your first E2E test. It might seem simple, but you've just verified that your app actually loads and displays content.

Testing Real User Behaviour

Static content is nice, but let's test some actual interactions. Remember that counter button in the default Vite template? Let's make sure it actually counts:

describe('Counter', () => { it('should increment the count when clicked', () => { cy.visit('/') cy.get('button').contains('count is').click() cy.get('button').should('contain.text', 'count is 1') }) }) 
Enter fullscreen mode Exit fullscreen mode

This test clicks the button and verifies that the count goes up.

Testing Async Code and APIs

Most real-world apps don't just display static content—they fetch data from APIs, and that's where bugs are.

Let's say you have a component that fetches users when it mounts:

// ExampleComponent.tsx import { useEffect, useState } from 'react' export function ExampleComponent() { const [users, setUsers] = useState<string[]>([]) const [loading, setLoading] = useState(true) useEffect(() => { fetch('/api/users') .then(res => res.json()) .then(data => { setUsers(data) setLoading(false) }) .catch(() => setLoading(false)) }, []) if (loading) return <div>Loading...</div>  return ( <ul data-cy="user-list"> {users.map(user => ( <li key={user} data-cy="user-item">{user}</li>  ))} </ul>  ) } 
Enter fullscreen mode Exit fullscreen mode

Now here's the interesting part—Cypress can intercept and mock API calls:

describe('User List', () => { it('should display users from the API', () => { // Mock the API response cy.intercept('GET', '/api/users', { statusCode: 200, body: ['John', 'Jane', 'Robert'] }).as('getUsers') cy.visit('/') // Wait for the API call to complete cy.wait('@getUsers') // Verify the users are displayed cy.get('[data-cy="user-list"]').should('be.visible') cy.get('[data-cy="user-item"]').should('have.length', 3) cy.contains('Alice').should('be.visible') }) it('should handle API failures gracefully', () => { cy.intercept('GET', '/api/users', { statusCode: 500, body: { error: 'Server error' } }).as('getUsersError') cy.visit('/') cy.wait('@getUsersError') // Test your error handling cy.contains('Something went wrong').should('be.visible') }) }) 
Enter fullscreen mode Exit fullscreen mode

The cy.intercept() method is amazing. You can test both success and failure scenarios without needing a real API.

Best Practices That Actually Matter

Use data-cy attributes religiously.. CSS classes change, text content gets translated, but data-cy="submit-button" stays constant:

// Good cy.get('[data-cy="submit-button"]').click() // Fragile cy.get('.btn-primary').click() cy.contains('Submit').click() 
Enter fullscreen mode Exit fullscreen mode

Reset your state before each test. Use beforeEach() to clear out any leftover state:

beforeEach(() => { cy.clearLocalStorage() cy.clearCookies() // Reset any global state your app might have }) 
Enter fullscreen mode Exit fullscreen mode

Think like a user, not a developer. Test the journey, not the implementation. Don't test that a function was called—test that the user sees what they expect.

Mock everything you don't control. Real APIs are slow, unreliable, and might not have the data you need for testing. Mock them for testing.

Conclusion

Testing feels daunting, we all know, but here's the thing: every minute you spend writing tests now saves you hours of debugging later. And with Cypress, those tests actually run in a real browser, so you can trust that they're testing what your users will experience. If you like this blog and want to learn more about Frontend Development and Software Engineering, you can follow me on Dev.to.

Top comments (0)