DOM testing Next.js applications
Tags: nextjs, testing, react
Published: 2020-09-26 22:27:53

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

## The missing tile

[Next.js][next] 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][react-testing-library] or [Enzyme][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**][next-docs-data-fetching] (`getServerSideProps` and `getStaticProps`) providing the expected `context` object
- **mocking [`NextRouter` object][next-docs-router-object]** with relevant current route data (`path`, `params`, `querystring`...) when the tested page makes use of Next's [`Link`][next-docs-link], [`useRouter` or `withRouter`][next-docs-use-router]
- hoping that **page file names** are 100% correct, since Next.js relies on those to configure file system routing
- rendering **custom [App][next-docs-custom-app] and [Document][next-docs-custom-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][jsdom]?

## Write tests. Not too many. Mostly integration.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/t2w9qfa09rp86352hy4u.jpg)

> 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][next-gh-discussion-tests] consists of running e2e tests against a fully fledged instance of the app.

With the rise of tools like [Cypress][cypress] and [Test Café][test-cafe], e2e tester lives became significantly easier but, as everything in life, [there's no silver bullet™][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 🔗][kentcdodds-testing-strategies].

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/cbot3v9lpgjagh8dj8v5.png)

## 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:

```js
const Page = getNextJsPage('/blog/1');
render(Page);
```

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][next-page-tester-gh], an utility library which enables approaching tests like:

```js
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');
  });
});
```

next-page-tester is [available on NPM][next-page-tester-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][next-page-tester-gh] to see how it works or lend hand :).

[next]: https://nextjs.org/
[react-testing-library]: https://github.com/testing-library/react-testing-library
[enzyme]: https://github.com/enzymejs/enzyme
[next-docs-data-fetching]: https://nextjs.org/docs/basic-features/data-fetching
[next-docs-custom-app]: https://nextjs.org/docs/advanced-features/custom-app
[next-docs-custom-document]: https://nextjs.org/docs/advanced-features/custom-document
[next-docs-router-object]: https://nextjs.org/docs/api-reference/next/router#router-object
[next-docs-link]: https://nextjs.org/docs/api-reference/next/link
[next-docs-use-router]: https://nextjs.org/docs/api-reference/next/router#userouter
[next-gh-discussion-tests]: https://github.com/vercel/next.js/discussions?discussions_q=tests
[cypress]: https://www.cypress.io/
[test-cafe]: https://devexpress.github.io/testcafe/
[no-silver-bullet]: https://en.wikipedia.org/wiki/The_Mythical_Man-Month#No_silver_bullet
[jsdom]: https://github.com/jsdom/jsdom
[kentcdodds-testing-strategies]: https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests
[next-page-tester-gh]: https://github.com/toomuchdesign/next-page-tester#next-page-tester
[next-page-tester-npm]: https://www.npmjs.com/package/next-page-tester