Testing Next.js components using Cypress
Tags: nextjs, beginners, testing, react
Published: 2023-09-05 06:08:42

The popularity of JavaScript frameworks is a testament to the flexibility they offer developers, allowing them to build complex applications. An example is Next.js, a React-based framework with pre-rendering, automatic code-splitting, and greater application scaling flexibility.

Every developer's goal is to build software that works. With a good JavaScript framework and adequate testing techniques, a developer can build software that works flawlessly. Testing enables developers to write efficient code, which saves them hours of debugging and eventually reflects in overall software quality.

[Cypress](https://www.cypress.io/) is an end-to-end testing framework that creates tests for modern web applications, debugs them visually, and automatically runs them in continuous integration builds.

###What we’ll build

This post reveals the similarities and differences between Cypress testing techniques and how to run tests in a Next.js application. The complete source code of this project is on [GitHub](https://github.com/ugwutotheeshoes/next-cypress-demo). Fork it to get started quickly.

###Prerequisite

A basic understanding of React.js and test UIs is needed to get the most out of this article.

##Getting started with Cypress

First, you'll bootstrap a new Next.js app by running the following command in your terminal:

```js
npx create-next-app <project-name>
```

Navigate into the project directory and install Cypress locally as a dev dependency.

```js
#navigate to the newly created app
cd <project-name>
    
#install cypress
npm install cypress --save-dev
    
#run cypress via npm
npx cypress open
```

> NOTE: `<project-name>` in the code snippet above stands for the app's name; you can call it whatever you want.

Once this is done, you'll need to include a way to launch Cypress from the terminal. You'll add a Cypress command to the `scripts` field in your `package.json` file to do this.

```js
{
"scripts": {
    "cypress:open": "cypress open"
  }
}
```
 
Invoking the command `npx cypress open` from your project root starts the Cypress Launchpad.

![Cypress Launchpad](https://paper-attachments.dropboxusercontent.com/s_B9E9168420653726697A5C060F93BC50343E4CC976760106C9B8F94705E3FC64_1678477270474_the-launchpad.png)

Before you start writing your first test, you'll have to choose what test your application requires. Cypress offers two options: **End-to-End** and **Component Testing**. 

![Testing Types](https://paper-attachments.dropboxusercontent.com/s_B9E9168420653726697A5C060F93BC50343E4CC976760106C9B8F94705E3FC64_1678477422119_choose-testing-type.png)

Each option has its advantages and disadvantages, and the decision will be based on the needs of the task you're attempting to accomplish. Ultimately, your app will have a combination of both tests, but how do you choose right now?

Let's review each of these test types, their benefits, and what to consider when testing your Next.js application.

###End-to-End Testing

E2E Testing is a technique for testing your app from the web browser to the back end, as well as interactions with third-party APIs and services. These tests ensure that your entire software works as a unit.

Cypress does end-to-end testing work in the same way users interact with your app using a real browser to visit URLs, view content, click on links and buttons, and so on. This testing method helps guarantee that your tests and the user's experience are consistent. Tests are written in code and use an API to replicate what a real user would do.

**Benefits of End-to-End tests**

- Ensures your app functions as a unit.
- Testing corresponds to the user experience.
- Developers or QA Teams can write it.
- It can also be used for integration testing.

**Considerations for End-to-End tests**

- More complex and time-consuming to set up, execute, and maintain.
- Provide testing infrastructure in CI.
- Testing specific situations necessitates additional setup.


###Writing your first E2E test

Cypress supports most browsers, including Chrome, Firefox, and Microsoft's new Edge browser. Cypress's execution speed and debugging capabilities are enhanced because the tests run within the browser. While the tests are performed in the browser, Cypress uses a node server as a proxy. 

The browser and the Cypress Node process continually communicate to do duties on each other's behalf, such as simulating network requests from tests. Cypress test suites may be readily integrated into any CI/CD pipeline to perform end-to-end tests during their nightly cycles, per pull request, or other triggered events.

![End-to-End Testing Launchpad](https://paper-attachments.dropboxusercontent.com/s_B9E9168420653726697A5C060F93BC50343E4CC976760106C9B8F94705E3FC64_1678485676742_Cypress+10_03_2023+23_01_02.png)

Cypress has provided some default tests to run your first E2E test in the Next.js app, as shown below. 

![Cypress default tests](https://paper-attachments.dropboxusercontent.com/s_B9E9168420653726697A5C060F93BC50343E4CC976760106C9B8F94705E3FC64_1678485808437_33109677-d22d-4286-a3bc-fa98428dfb1b.png)

Instead of using the provided tests, head to the file directory: `cypress/e2e` and create a new file called `sample.cy.js`. After creating the file, copy and paste the code snippet below.

```js
#cypress/e2e/1-getting-started/sample.cy.js
    
describe('My First Test', () => {
  it('clicks the link "type"', () => {
    cy.visit('https://example.cypress.io')
    cy.contains('type').click()
  })
})
```

The test visits a webpage and clicks on a link located on the webpage.

![First test](https://paper-attachments.dropboxusercontent.com/s_B9E9168420653726697A5C060F93BC50343E4CC976760106C9B8F94705E3FC64_1678570094293_8ef47b5c-5299-4265-a1c6-52913e2685c5.png)

###Component Testing

Component tests differ from end-to-end testing in that, rather than visiting a URL to load up an entire app, a component can be "mounted" and evaluated independently. This allows you to focus solely on testing the component's functionality and not on any intricacies associated with testing a component as part of a bigger application.

Modern web frameworks provide ways to write applications by splitting them into smaller logical units known as components. Components might be as simple as a button to something as complicated as a registration form. Because of their nature, components are easily testable, which is where Cypress Component Testing comes in.

Nevertheless, even if all of your component tests pass, this does not imply that your application functions properly. Component tests offer nothing to confirm that all of your app's layers work together correctly. As a result, a well-tested program includes both end-to-end and component tests, with each set of tests specializing in what it does best.

**Benefits of Component Tests**

- It is simpler to test components in isolation.
- Quick and dependable.
- It is simple to create customized scenarios in tests.
- They don't rely on an external system to function.

**Considerations for Component Tests**

- Do not guarantee overall app quality.
- Do not use external APIs/Services.
- Often written by developers working on the component.

###Writing your first Component test

Navigate to the file directory, `pages` and create two files - `Counter.js` and `Counter.cy.js`. These files are the app’s component and testing files. Head to `Counter.js` and paste the code snippet below.

```js
import { useState } from "react";
export default function Counter({ initial = 0, onChange = () => {} }) {
  const [count, setCount] = useState(initial);
  const handleIncrement = () => {
    const newCount = count + 1;
    setCount(newCount);
    onChange(newCount);
  };
  const handleDecrement = () => {
    const newCount = count - 1;
    setCount(newCount);
    onChange(newCount);
  };
  return (
    <div>
      <h1>Counter</h1>
      <button data-cy="decrement" className="btn-primary" onClick={handleDecrement}>
        -
      </button>
      <span data-cy="counter" className="counter">{count}</span>
      <button data-cy="increment" className="btn-primary" onClick={handleIncrement}>
        +
      </button>
    </div>
  );
}
```

This will create a simple counter to increase or decrease a number. The application will look like this after applying the code snippet above.

![Counter](https://paper-attachments.dropboxusercontent.com/s_B9E9168420653726697A5C060F93BC50343E4CC976760106C9B8F94705E3FC64_1678570822661_Create-Next-App+3.png)

Now, let’s create a test script for the `Counter.js` file. Head to the `Counter.cy.js` file and write a test to mount the `Counter.js` component, then click on the increment button. After clicking the increment button, Cypress should check if the counter has a value of 1.  Cypress should also do the same in a decrement scenario.

```js
import React from 'react'
import Counter from './Counter'
   
it('when the increment button is pressed, the counter is incremented', () => {
  cy.mount(<Counter />)
  cy.get('[data-cy=increment]').click()
  cy.get('[data-cy=counter]').should('have.text', '1')
})
it('when the decrement button is pressed, the counter is decremented', () => {
  cy.mount(<Counter />)
  cy.get('[data-cy=decrement]').click()
  cy.get('[data-cy=counter]').should('have.text', '-1')
})
```

Head to the Component Test Launchpad and access the component testing page to run the test file you just created.

![Component Testing Launchpad](https://paper-attachments.dropboxusercontent.com/s_B9E9168420653726697A5C060F93BC50343E4CC976760106C9B8F94705E3FC64_1678485561684_Cypress+10_03_2023+22_58_42.png)
![Component testing page](https://paper-attachments.dropboxusercontent.com/s_B9E9168420653726697A5C060F93BC50343E4CC976760106C9B8F94705E3FC64_1678485906274_686d5706-96f4-4a0c-a209-c0fde02d3295.png)


The test visits the component file and performs the test from its testing file.

![Counter Test](https://paper-attachments.dropboxusercontent.com/s_B9E9168420653726697A5C060F93BC50343E4CC976760106C9B8F94705E3FC64_1678572050959_b41e71ca-2dc1-4038-8a13-9da18a8c7030.png)

###Testing Type Comparison

While both end-to-end testing and component testing are valuable testing methodologies, they differ in terms of their scope, granularity, and complexity. These are comparisons of the two testing types:

|                                  | E2E                                              | Component                                            |
| -------------------------------- | ------------------------------------------------ | ---------------------------------------------------- |
| What is tested in the app        | All app layers                                   | Individual component                                 |
| Characteristics of the test type | Comprehensive, slower, more susceptible to flake | Specialized, quick, reliable                         |
| Test type use                    | Verifying app works as a cohesive whole          | Testing the functionality of an individual component |
| Written by                       | Developers, QA Team, SDETs                       | Developers, Designers                                |
| CI Infrastructure                | Often requires complex setup                     | None needed                                          |
| Initialization Command           | `cy.visit(url)`                                  | `cy.mount(<MyComponent />)`                          |

###Testing API routes

When working with larger applications, you may encounter the need to test routes and endpoints. This is also possible with Cypress. Navigate to the pages folder in your application and create a new file called `books.js`, which will store the data and functions you need for your API:

```js
// pages/api/books.js
    
export default function books(req, res) {
  res.statusCode = 200;
  res.setHeader("Content-Type", "application/json");
  return res.json([
    {
      id: 1,
      book: "The Firm",
      author: "John Grisham",
    },
    {
      id: 2,
      book: "Cracking the PM interview",
      author: "Jackie Bavaro",
    },
    {
      id: 3,
      book: "Fools Die",
      author: "Mario Puzo",
    },
  ]);
}
```
Next, let’s create a script for testing. Navigate to the `e2e/1-getting-started` folder in `cypress`, where you’ll create the tests for the API routes: 

```js
//cypress/e2e/1-getting-started/books.cy.js
    
describe("Book test", () => {
  it("Confirms the number of books in your local library", () => {
    cy.visit("http://localhost:3000");
    cy.request("GET", "api/books").as("books");
    cy.get("@books").should((response) => {
      expect(response.status).to.eq(200);
      expect(response).to.have.property("headers");
      expect(response).to.have.property("duration");
      expect(response.body).to.have.length(3);
    });
  });
});
```

The test expects the following from the `books` endpoint’s response:

- Response status equals `200`.
- The API response to include a header.
- The API response body should contain only three objects.
- The test should include response time.

Launch the E2E Test server and run the tests like before.

![](https://paper-attachments.dropboxusercontent.com/s_B9E9168420653726697A5C060F93BC50343E4CC976760106C9B8F94705E3FC64_1678568580415_17d799af-5c9c-4453-80bd-a839971357bd.png)


You should have this as your result after performing the test.  

![](https://paper-attachments.dropboxusercontent.com/s_B9E9168420653726697A5C060F93BC50343E4CC976760106C9B8F94705E3FC64_1678568925964_25640f2c-12de-4a6d-a7d2-5751cfe7147d.png)

###Test Isolation

Test Isolation refers to the ability to execute tests independently of one another without being affected by the state of other tests or the application itself. Developers can ensure that other tests or external influences do not influence the results of each test by executing each test in a clean environment. This facilitates the identification and correction of bugs and the efficient collaboration of team members.

**Test Isolation in End-to-End Testing**
End-to-end testing in Cypress supports enabling or disabling test isolation to specify whether a suite of tests should run in a clean browser context or not. The test isolation is a global configuration that can be overridden at the `describe` level for End-to-End testing with the `testIsolation` option.

```js
#cypress.config.js
   
const { defineConfig } = require('cypress')
    
module.exports = defineConfig({
  e2e: {
    testIsolation: true
  },
})
```

When test isolation is enabled, Cypress resets the browser context before each test by:

- Clearing the DOM state by visiting `about:blank`.
- Clearing [cookies](https://docs.cypress.io/api/cypress-api/cookies) in all domains.
- Clearing [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) in all domains.
- Clearing [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) in all domains.

Because the test starts in a new browser context, you must return to your application and perform the set of interactions required to build the DOM and browser state for each test.

Also, when establishing a browser session, the `cy.session(0)` command will inherit this configuration and erase the page and current browser context. This is done so that tests can pass when run independently or in a randomized order.

When test isolation is disabled, Cypress doesn't change the browser context before the test begins. The page does not distinguish between tests and cookies; therefore, local and session storage will be accessible across all tests in that suite. Also, the `cy.session()` command clears only the current browser context while establishing a browser session.

**Test Isolation in Component Testing**
Cypress doesn’t support global configuration for test isolation in component testing.
When running component tests, Cypress always resets the browser context before each test by:

- Unmounting the rendered component under the test.
- Clearing [cookies](https://docs.cypress.io/api/cypress-api/cookies) in all domains.
- Clearing [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) in all domains.
- Clearing [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) in all domains.

It is crucial to remember that while disabling test isolation may increase overall end-to-end test performance, it may also allow the state to "leak" between tests. This can make recent tests reliant on the results of previous tests, potentially leading to misleading test failures. While using this mode, it is important to be particularly attentive to how tests are written and ensure that tests continue running independently.

##Conclusion
Using Cypress to test Next.js apps can assist developers in creating high-quality, reliable web applications that fulfill user experience and standards and perform well in production.

##Resources
The following resources can help you get started with Cypress:
- [Cypress Documentation](https://docs.cypress.io/)
- [Cypress Configuration for Next.js](https://docs.cypress.io/guides/component-testing/react/overview#Nextjs)