DEV Community

loading...
Cover image for DOM testing Next.js applications

DOM testing Next.js applications

Andrea Carraro
Front-end engineer @TUI-Musement. Previously @UALA, @XING, @Mosaicoon. Human.
・Updated on ・4 min read

This article analyses the current status of DOM tests in Next.js and presents a utility library (next-page-tester) to get back DOM integration tests alongside Next.js apps.

The missing tile

Next.js comes with an outstanding tool belt out of the box, providing any web project with great dev experience and productivity since day one.

The value provided by Next.js is truly priceless, but there's a missing tile: DOM integration testing (React Testing Library or Enzyme tests, to put it simple) is quite uneffective and fragmented when it comes to Next.js.

Let's see why.

Why

An average Next.js project is made of disconnected pieces which are glued together by Next.js internals on next dev or next build.

This means less boilerplate code πŸ‘but also no explicit relations among separate parts πŸ˜”. In other words: only Next.js knows how to put your application together.

This affects the way unit tests for Next.js apps are written: isolated tests targeting different parts and optimistically mocking what's in-between.

Let's be more explicit. This involves:

  • testing individual page components providing the expected server-side generated props
  • testing pages' data fetching methods (getServerSideProps and getStaticProps) providing the expected context object
  • mocking NextRouter object with relevant current route data (path, params, querystring...) when the tested page makes use of Next's Link, useRouter or withRouter
  • hoping that page file names are 100% correct, since Next.js relies on those to configure file system routing
  • rendering custom App and Document components, if the case

All this is feasible but, where are the times when we could write integration tests by rendering the whole components tree in Node.js with JSDOM?

Write tests. Not too many. Mostly integration.

Alt Text

https://twitter.com/rauchg/status/807626710350839808

Since business logic is spread among different disconnected places (and the fact that Next.js runs a web server), the currently suggested way of testing Next.js applications consists of running e2e tests against a fully fledged instance of the app.

With the rise of tools like Cypress and Test CafΓ©, e2e tester lives became significantly easier but, as everything in life, there's no silver bulletβ„’ and browser tests make no exception. It'd be cool to be able to grab the right tool for the right task.

The internet is packed with resources documenting the trade-offs of different testing strategies. It's mostly a matter of cost, speed and determinism. This is a popular article by Kent C. Dodds to name one πŸ”—.

Alt Text

DOM Integration tests for Next.js

Ideally, a promising integration testing routine might consist of testing Next.js apps by route: given a route path, I receive the matching page element ready to be tested with any DOM testing library:

const Page = getNextJsPage('/blog/1');
render(Page);
Enter fullscreen mode Exit fullscreen mode

In order to get to such testing routine we need to replicate part of Next.js glue:

  • resolving provided routes into the matching page component
  • calling Next.js data fetching methods (getServerSideProps, getInitialProps or getStaticProps)
  • set up the expected mocked next/router provider
  • wrapping page with custom _app/_document component
  • instantiating page component with the expected props
  • Emulate client side navigation via Link, router.push, router.replace

I put together this logic in next-page-tester, an utility library which enables approaching tests like:

import { screen, fireEvent } from '@testing-library/react';
import { getPage } from 'next-page-tester';

describe('Blog page', () => {
  it('renders blog page', async () => {
    const { render } =  await getPage({
      route: '/blog/1',
    });

    render();
    expect(screen.getByText('Blog')).toBeInTheDocument();

    fireEvent.click(screen.getByText('Link'));
    await screen.findByText('Linked page');
  });
});
Enter fullscreen mode Exit fullscreen mode

next-page-tester is available on NPM and aims to make DOM integration tests first class citizens in Next.js.

It's written in Typescript, fully tested and open to receive any feedback from curious Next.js users. Hop over to next-page-tester GitHub page to see how it works or lend hand :).

Discussion (5)

Collapse
vvo profile image
Vincent Voyer

Thanks for the article, testing Next.js applications is not a subject that is often talked about. I added your article in the latest Next.js News: twitter.com/vvoyer/status/13182876...

Collapse
maciekgrzybek profile image
Maciek Grzybek

Amazing stuff buddy, I was looking for something like that :) Any chance you need a hand with the project? I'd like to contribute :)

Collapse
toomuchdesign profile image
Andrea Carraro Author

Hi Maciek! We definitely encourage and support external contributions. We wouldn't be able to follow Next.js evolution without the help of next-page-tester's users.
You're welcome on board.

Collapse
itaditya profile image
Aditya Agarwal

This looks like a great approach. Very excited to give it a go.

Collapse
dschmidtadv profile image
Dietrich Schmidt

This is really great, I had been struggling with aforementioned issues when I discovered this!