How to test Next.js apps


In this tutorial, we’ll be using Cypress to write End to End tests for the app that was built as a part of How to Build a CMS-powered Marketing Website while Avoiding WordPress. An online demo is hosted at Netlify and the code for this tutorial is available on Github. If you’re looking for the code of the hosted demo, you can also find it on Github.

Writing tests for your app is really necessary as it gives you the confidence to ship features faster. It essentially helps you remove critical bugs from your product. Writing End to End tests ensures that you test your app how a real user would. End to End tests also give you the confidence that both your backend and frontend are working as expected.

What is Next.js?

Next.js is one of the most popular React Frameworks that are available now. Using Next.js, you can write Server Side Rendered applications with zero setup.

Cypress Testing

Cypress is a developer-friendly JavaScript End to End testing framework. It doesn’t use Selenium. It’s built on a new architecture from the ground up. Whereas Selenium executes remote commands through the network, Cypress runs in the same run-loop as your application.


Getting Started

We need to setup Cypress with our application before we start writing any of tests. First, you need to clone the repository which contains code for the site that will be tested.

/** * Clone the repository */ git clone https://github.com/ghoshnirmalya/testing-next.js-apps /** * Go inside the project directory */ cd testing-next.js-apps

You will need to create a .env file and add your ButterCMS API key there:

BUTTER_CMS_API_KEY=something

The setup for running the demo app is complete. Now, let’s install Cypress.


Installing Cypress

Installing Cypress is pretty straight-forward. If you machine is compatible with the system requirements of Cypress, you can install Cypress using:

/** * Go inside your project directory */ cd your/project/path /** * Using npm */ npm install cypress --save-dev


If you prefer
yarn, you can install Cypress using:

/** * Go inside your project directory */ cd your/project/path /** * Using yarn */ yarn add cypress --dev


Running Cypress

You can run Cypress using the following command:

./node_modules/.bin/cypress open


You can also add a script in you
package.json file to run Cypress:

"scripts": { .... "cypress:open": "cypress open" }

Now, you can run using:

/** * Using npm */ npm run cypress:open

If you prefer yarn, you can run Cypress using:

/** * Using yarn */ yarn cypress:open

Once you run the script, Cypress will generate a bunch of files for your app:

undefined

As soon as this information shows up, the Cypress Test Runner will launch.

undefined

You will also see that Cypress will start running a new instance of Chrome for testing. For now, you can stop running the tests by clicking Ctrl+C in your terminal.

Adding basic configurations

Before we start writing any tests, we need to remove some default files which Cypress generated for us. Also, we’ll configure the default Cypress setup a little bit so that it helps us in writing tests.

First, you can remove the examples directory present in cypress/integration/examples. We won’t be needing any specific plugins for this tutorial. However, Cypress has an amazing set of plugins. One last thing that you need to configure is add the following to your cypress.json file:

{ "baseUrl": "http://localhost:3000" }

This baseUrl is used as prefix for cy.visit() or cy.request() command’s url. You can check our Cypress documentation for more details on the options that it provides.

Also, let’s create a index-page.js file inside cypress/integration directory. This file will consist of all the tests for our Home page. For now, we need to add the following inside this file:

describe("Index page", () => { /* * Visits the page before each test */ beforeEach(() => { cy.log(`Visiting http://localhost:3000`); cy.visit("/"); }); });

This means that before running each test Cypress will visit the page which is configured in the cypress.json file’s baseUrl configuration. For this tutorial, it will be http://localhost:3000/ since our app is running on that port. We want to visit this page before each test so that we can ensure that one test is not affecting any other tests.

That’s all the configuration we need to start writing our tests.


Writing tests for the home page

For the Header section, we would only test if this section contains a logo. The test will look something like:

/** * Header section */ it("should have a logo", () => { cy.get(".brand.header-brand img").should("have.length", 1); });

For the Hero section, we would only test if this section contains a title, description and a button. The test will look something like:

/** * Hero section */ it("should have a hero section with a title, description and a button", () => { cy.get(".hero .hero-title").should("have.length", 1); cy.get(".hero .hero-paragraph").should("have.length", 1); cy.get(".hero .hero-cta > .button").should("have.length", 1); });

For the Clients section, we would only test if this section contains two images. The test will look something like:

/** * Clients section */ it("should have a clients section with two images", () => { cy.get(".clients img").should("have.length", 2); });

For the Features section, we would only test if this section contains a title, description and four cards with an image, title and description. The test will look something like:

/** * Features section */ it("should have a features section with a title, description and four cards with an image, title and description", () => { cy.get(".features .section-title").should("have.length", 1); cy.get(".features .section-paragraph").should("have.length", 1); cy.get(".features .feature").should("have.length", 4); cy.get(".features .feature .feature-icon").should("have.length", 4); cy.get(".features .feature .feature-title").should("have.length", 4); cy.get(".features .feature .text-sm").should("have.length", 4); });

For the Testimonials section, we would only test if this section contains a title and two cards with a description and author name. The test will look something like:

/** * Testimonials section */ it("should have a testimonials section with a title and two cards with a description and author name", () => { cy.get(".testimonials .section-title").should("have.length", 1); cy.get(".testimonials .testimonial").should("have.length", 2); cy.get(".testimonials .testimonial .testimonial-body").should( "have.length", 2 ); cy.get(".testimonials .testimonial .testimonial-name").should( "have.length", 2 ); });

For the Pricing section, we would only test if this section contains a title and one card with a description, price and a CTA. The test will look something like:

/** * Pricing section */ it("should have a pricing section with a title and one card with a description, price and a CTA", () => { cy.get(".pricing .section-title").should("have.length", 1); cy.get(".pricing .pricing-table").should("have.length", 1); cy.get(".pricing .pricing-table .pricing-table-title").should( "have.length", 1 ); cy.get(".pricing .pricing-table .pricing-table-price").should( "have.length", 1 ); cy.get(".pricing .pricing-table .pricing-table-cta").should( "have.length", 1 ); });

For the FAQs section, we would only test if this section contains a title and two FAQs. The test will look something like:

/** * FAQs section */ it("should have a FAQs section with a title and two FAQs", () => { cy.get(".pricing .pricing-faqs h4").should("have.length", 1); cy.get(".pricing .pricing-faqs .accordion li").should("have.length", 2); });

For the CTA section, we would only test if this section contains a title and a button. The test will look something like:

/** * CTA section */ it("should have a CTA section with a title and a button", () => { cy.get(".cta .section-title").should("have.length", 1); cy.get(".cta .cta-cta").should("have.length", 1); });

For the Footer section, we would only test if this section contains a logo and three social links. The test will look something like:

/** * Footer section */ it("should have a footer section with a logo and three social links", () => { cy.get(".site-footer .brand img").should("have.length", 1); cy.get(".site-footer .footer-social-links li").should("have.length", 3); });

Conclusion

At this point, you should be able to understand how to write End to End tests for apps built with Next.js as well as any other framework with Cypress.

I hope this tutorial helps you in your future projects. Please feel free to share your feedback in the comments section below.