Boosting Next.js Performance: A Guide to E2E and Component Testing with Cypress

Prajeet Kumar Thakur
readytowork, Inc.
Published in
8 min readSep 27, 2023

Introduction

Testing is a fundamental aspect of software development, ensuring code accuracy and reliability. In the context of Next.js, a framework for React apps, testing practices are vital. Cypress, a versatile test runner, aids in both End-to-End and Component Testing.

Cypress empowers Next.js developers to verify applications comprehensively. It enables simulation of user interactions in E2E (End to end) tests and isolates UI components for testing. This enhances user path functionality and code modularity.

Adopting Cypress enhances Next.js development by spotting issues early. This proactive approach fosters maintainable, high-quality code, resulting in more robust applications.

What we will learn:

  1. About E2E and Component testing
  2. Creating a Next.js application with Cypress and
  3. Using Cypress to write and run an E2E and a Component test of our Next.js application
  4. Utilizing the Interface and CLI of Cypress to run tests

Let’s create a Next.js app with Cypress and run our first E2E test.

Setting up the codebase

  1. Open your terminal and run this command to get started with Cyprus
npx create-next-app@latest --example with-cypress with-cypress-app
Fig 1: Screenshot of terminal installing cypress

2. Remove the boilerplate code of the index.tsx file and replace it with this:

import Link from 'next/link'

export default function Home() {
return (
<nav>
<h1>Homepage</h1>
<Link href="/about">About</Link>
</nav>
)
}

3. Remove the boilerplate code from the About page and paste this:

import Link from "next/link";
export default function About() {
return (
<div>
<h1>About Page</h1>
<Link href="/">Homepage</Link>
</div>
)
}

Steps 2 and 3 are done in order to make the code simpler and easier to read, as we are not focused on the styling and JSX/TSX, but rather on the testing part of our application that we cover in our next step.

4. Next, add the cypress script to the package.json file:

cypress: "cypress open"
Fig 2: Screenshot of adding the cypress script in the package.json file

E2E testing

End-to-end testing, also known as E2E testing, is a way to make sure that applications behave as expected and that the flow of data is maintained for all kinds of user tasks and processes. This type of testing approach starts from the end user’s perspective and simulates a real-world scenario.

Fig: Visualizing E2E testing

Moving back to our code, we will write an E2E test for the navigation of the application.

5. Let's look at the test file created from the boilerplate code:

// Cypress E2E Test
describe('Navigation', () => {
it('should navigate to the about page', () => {
// Start from the index page
cy.visit('http://localhost:3000/')
// Find a link with an href attribute containing "about" and click it
cy.get('a[href*="about"]').click()
// The new url should include "/about"
cy.url().should('include', '/about')
// The new page should contain an h1 with "About page"
cy.get('h1').contains('About Page')
})
})
// Prevent TypeScript from reading file as legacy script
export {}typ

Description of the test:

The purpose of this test is to ensure that the navigation to the “About” page on a web application is working as expected.

Let’s break down the code step by step:

1. describe(‘Navigation’, () => { … }): This is a Cypress test suite description. It groups related tests under the category of “Navigation.” Within this suite, there is a single test case defined using `it`.

2. it(‘should navigate to the about page’, () => { … }): This is a test case description. It describes the specific behavior that is being tested, which is whether the navigation to the “About” page works correctly.

3. cy.visit(‘http://localhost:3000/'): This Cypress command instructs the browser to navigate to the specified URL, in this case, “http://localhost:3000/". This is the starting point of the test, assuming that the web application is being served locally on port 3000.

4. cy.get(‘a[href*=”about”]’).click(): This command searches for an anchor (`<a>`) element whose `href` attribute contains the substring “about” and clicks on it. This simulates clicking a link that leads to the “About” page.

5. cy.url().should(‘include’, ‘/about’): This command checks the current URL of the page and asserts that it includes the string “/about”. This verifies that the navigation has successfully taken the user to the “About” page.

6. cy.get(‘h1’).contains(‘About Page’): This command selects an `<h1>` element on the current page and checks whether its content contains the text “About Page”. This verifies that the expected content is present on the “About” page.

7. export {}: This line is used to prevent TypeScript from treating the file as a legacy script. It’s a way to ensure that TypeScript doesn’t complain about any syntax it might not understand, as this is not actual TypeScript code.

In summary, this Cypress test navigates to a local web application, clicks a link that leads to the “About” page, checks that the URL has changed to include “/about”, and confirms that the “About Page” heading is present on the new page. This way, the test verifies that the navigation to the “About” page functions correctly.

Running the E2E test

In order to run the test, it is required that the Next.js server is running prior to starting Cypress. Hence, type these commands in the terminal to start the installed codebase:

Start the Next.js server

cd with-cypress-app
npm run dev

After running these commands, the server should start at port 3000 by default. Make sure that any other application is not using the same port as it will prevent our Next.js application from running.

Run the test

npm run cypress

The Cypress pop-up window will appear prompting you to select a testing type. Choose the E2E testing for our example.

Fig 3: Cypress prompt to select testing type

Next, choose the browser. We will be using Chrome for our E2E testing.

Fig 4: Cypress prompt to select browser for testing

As we can see our app is listed here that we just created. Click on it to run the test. (Make sure that the app is running)

Fig 5: Screenshot of listing the available apps for testing

Running the tests:

Fig 6: Running the test

As we can see in the above screenshot, the test is successful as the About page is being navigated successfully.

Component tests

The above example involved the E2E testing, now we are going to look at component tests where we test individual components.

Component testing is a form of closed-box testing, meaning that the test evaluates the behavior of the program without considering the details of the underlying code. Component testing is done on the section of code in its entirety after the development has been completed.

Fig: Visualizing Component testing

You will have noticed that running Cypress so far has opened an interactive browser which is not ideal for CI environments. You can also run Cypress headlessly using the cypress run command:

npm run e2e:headless
Fig 7: Screenshot of Cypress tests running in command line interface

1. Now, in the pages folder, create a file named about.cy.tsx, and paste the following code:

import About from "./about"

describe('<AboutPage />', () => {
it('should render and display expected content', () => {
// Mount the React component for the About page
cy.mount(<About />)

// The new page should contain an h1 with "About page"
cy.get('h1').contains('About Page')

// Validate that a link with the expected URL is present
// *Following* the link is better suited to an E2E test
cy.get('a[href="/"]').should('be.visible')
})
})

This code is a test script written using the Cypress testing framework to perform end-to-end (E2E) testing on a React application. The purpose of this test is to check if the <About/> the component renders correctly and if it contains expected content.

Let’s break down the code step by step:

  1. Import Statement:

import About from “./about”;
This line imports the `About` component from a file named `”about.js”` or `”about.jsx”` in the same directory as the test script.

2. Test Suite Declaration:
describe(‘<AboutPage />’, () => {

This block starts the description of the test suite named `AboutPage`. The `describe` function is used to group related test cases together. In this case, it’s being used to group tests related to the `AboutPage` component.

3. Test Case Definition:
it(‘should render and display expected content’, () => {

This block defines a single test case within the `AboutPage` test suite. The `it` function is used to define an individual test case. The string `’should render and display expected content’` is the description of what this test case is expected to achieve.

4. Mounting the Component:
cy.mount(<About />);
This line uses Cypress’s `cy.mount()` command to render the `About` component. In Cypress, the `cy` object provides a set of commands to interact with and manipulate elements in the DOM.

5. Assertion: Checking for an `<h1>` Element:
cy.get(‘h1’).contains(‘About Page’);
This line uses the `cy.get()` command to select the `<h1>` element in the rendered DOM. The `.contains()` method is then used to check if the text “About Page” is present within the selected `<h1>` element. This assertion checks if the expected content is displayed on the page.

6. Assertion: Checking for a Link:
cy.get(‘a[href=”/”]’).should(‘be.visible’);
This line uses the `cy.get()` command again to select an anchor `<a>` element with an `href` attribute equal to `”/”`. The `.should(‘be.visible’)` assertion is used to verify that the selected link is visible on the page.

In summary, this test script sets up a test suite called `AboutPage` and within it, a test case that:
- Renders the `About` component.
- Checks if the `<h1>` element contains the text “About Page”.
- Verifies that a link with an `href` attribute of `”/”` is visible on the page.

The purpose of this test is to ensure that the `About` component is rendering correctly and that it contains the expected content.

Run this command to run the component test for the About page:

npm run component:headless
Fig 8: Screenshot of running Component test in terminal
Fig 9: Screenshot of running Component test in terminal (Results)

Conclusion

I hope you followed along with the testing process. These are some key takeaways:

  1. Testing is a crucial aspect of software development. Test-driven development (TDD) proves its worth by ensuring code quality and maintainability.
  2. We explored both end-to-end (E2E) and component testing, exemplifying each approach. E2E testing verifies overall application functionality, while component testing enhances code modularity.
  3. For Next.js applications, effective testing is key to optimizing performance, resulting in better user experiences.

I hope this article was insightful. Please share your suggestions below; your feedback is invaluable.

References:

End-to-end testing — CircleCI

Component testing — CircleCI

How to perform End to end-testing

--

--