DEV Community

Cover image for Testing Next.js pages - Little Bits
Maciek Grzybek
Maciek Grzybek

Posted on • Updated on

Testing Next.js pages - Little Bits

Why?

Next.js is a super cool React framework, that gives you an amazing developer experience. In this episode, I'll show you how to test the Next pages with few useful libraries. This setup will allow us to create integration tests with mocking calls to the API. You can check the working example here.

Setup

First of all, set up your Next app with Typescript and React Testing Library. I explained how to do it in one of the previous episodes.

When it's done, install rest of the needed dependencies:

  • MSW - API mocking tool
  • Next Page Tester - DOM integration testing tool for Next.js
  • Axios - you can use any fetching library, but we will go with this one
npm i msw next-page-tester -D
npm i axios
Enter fullscreen mode Exit fullscreen mode

App

Create a simple homepage in pages/index.tsx. It will make a server-side call to the Stars Wars API to get the list of films and print them out.

import React from 'react';
import { GetServerSideProps, InferGetServerSidePropsType } from 'next';
import axios from 'axios';

export interface Film {
  title: string;
  director: string;
  release_date: string;
}

export interface FilmsResponse {
  results: Film[];
}

export default function Home({
  data,
  notFound,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  if (notFound) {
    return <div>Something went wrong, please try again</div>;
  }

  return (
    <div>
      <main>
        <ul>
          {data.results.map(({ title, release_date, director }) => (
            <li key={title}>
              <h2>{title}</h2>
              <span>Release date: {release_date}</span>
              <span>Director: {director}</span>
            </li>
          ))}
        </ul>
      </main>
    </div>
  );
}

export const getServerSideProps: GetServerSideProps<{
  data?: FilmsResponse;
  notFound?: boolean;
}> = async () => {
  try {
    const { data } = await axios.get<FilmsResponse>(
      'https://swapi.dev/api/films/'
    );
    if (!data.results) {
      return {
        props: { notFound: true },
      };
    }

    return {
      props: { data },
    };
  } catch (error) {
    return {
      props: { notFound: true },
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

Preparing mocks

In the test environment, we don't really want to hit the actual API so we will mock it with msw.

First of all, let's create a list of mocked films in __mocks__/mocks.ts

import { FilmsResponse } from '../pages';

export const mockedFilms: FilmsResponse = {
  results: [
    {
      title: 'A New Hope',
      release_date: '1977-05-25',
      director: 'George Lucas',
    },
    {
      title: 'The Empire Strikes Back',
      release_date: '1980-05-17',
      director: 'Richard Marquand',
    },
  ],
};
Enter fullscreen mode Exit fullscreen mode

Next, let's create server handlers (we define what msw should return when our app hit a specific URL). Let's create a new file test-utils/server-handlers.ts

import { rest } from 'msw';

import { API_URL } from '../config'; //'https://swapi.dev/api/films'
import { mockedFilms } from '../__mocks__/mocks';

const handlers = [
  rest.get(API_URL, (_req, res, ctx) => {
    return res(ctx.json(mockedFilms));
  }),
];

export { handlers };
Enter fullscreen mode Exit fullscreen mode

Short explanation:

  • rest.get(API_URL - when app send a GET request to the [https://swapi.dev/api/films](https://swapi.dev/api/films) endpoint
  • return res(ctx.json(mockedFilms)) - return the list of mocked films

Now, let's create a mock server that will run for our tests. Create a new file in test-utils folder names server.ts

import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { handlers } from './server-handlers';

const server = setupServer(...handlers);
export { server, rest };
Enter fullscreen mode Exit fullscreen mode

Then, in jest.setup.ts file, add the code that will be responsible for running the server:

import { server } from './test-utils/server';

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Enter fullscreen mode Exit fullscreen mode

If you want to learn more about msw tool check out their documentation, it's really good. Also, as usual, I recommend reading one of the Kent Dodds's blog post about mocking. It explains the topic really well.

Writing tests

Now, this is a really simple app, but I just want to show an example of how we can nicely test its behaviour. In this scenario, we only want to see if the films are printed on the screen and if it shows an error message when API returns something else than data. For that, we will use jest, react-testing-library and next-page-tester.

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

import { mockedFilms } from '../__mocks__/mocks';
import { server, rest } from '../test-utils/server';
import { API_URL } from '../config';

test('displays the list of films', async () => {
  const { render } = await getPage({ route: '/' });

  render();

  await waitFor(() => {
    mockedFilms.results.forEach(({ title, release_date, director }) => {
      expect(
        screen.getByRole('heading', { level: 2, name: title })
      ).toBeInTheDocument();
      expect(
        screen.getByText(`Release date: ${release_date}`)
      ).toBeInTheDocument();
      expect(screen.getByText(`Director: ${director}`)).toBeInTheDocument();
    });
  });
});

test('shows the error message when receive an error from the API', async () => {
  server.use(rest.get(API_URL, async (_req, res, ctx) => res(ctx.status(404))));

  const { render } = await getPage({ route: '/' });

  render();

  await waitFor(() => {
    expect(
      screen.getByText('Something went wrong, please try again')
    ).toBeInTheDocument();
  });
});
Enter fullscreen mode Exit fullscreen mode

As you can see, mocking the Next pages is really simple with next-page-tester tool. You can just simply pass the path as an argument, and it will render the whole page that's ready for testing. Check out the projects GitHub page for more details.

Also, notice how we overwrite the API server handler (instead of an actual data, we want to return a 404 status code when the app hits the API):

server.use(rest.get(API_URL, async (_req, res, ctx) => res(ctx.status(404))));
Enter fullscreen mode Exit fullscreen mode

Summary

As you can see, testing Next pages can be super fun and easy. These integrations tests are great for testing a general user journey, and are perfect addition to regular unit tests, where we can test more detailed scenarios.

Top comments (14)

Collapse
 
matiasbacelar98 profile image
Matias Bacelar

hello, great post !! there is not much information about testing in next js going around, I am having a bug if I use fetch instead of axios I guess it is because fetch is only available in the browser , i try using something like node fetch but I still get "fetch is not defined ", if you know any solution I'm all ears haha. Thank you

Collapse
 
maciekgrzybek profile image
Maciek Grzybek

Hey Matias thanks for that :) this bug you're talking, do you mean in app or in tests?

Collapse
 
matiasbacelar98 profile image
Matias Bacelar

In getServerSideProps when I use fetch and run the test it throws me "fetch is not defined", but only if I use fetch with axios the error does not appear

Thread Thread
 
maciekgrzybek profile image
Maciek Grzybek

If you could send me the code or even better - a link to repo, I can take a look and try to help you out :)

Thread Thread
 
matiasbacelar98 profile image
Matias Bacelar

I solve the problem using fetch-node thanks for answering anyway!

Thread Thread
 
alilop profile image
Alicia Lopez

Hello Matias I am having the same issue trying to test GetServerSideProps, but keep getting the "fetch is not defined". How did you solve it exactly via fetch-node? I am failing so far... Thank You in advace!

Thread Thread
 
maciekgrzybek profile image
Maciek Grzybek

Native fetch is only accessible in the browser and Next is using SSR, which means you have to use something that can be used in the server as well instead. Node fetch or axios for example

Thread Thread
 
matiasbacelar98 profile image
Matias Bacelar • Edited

As Maciek said, native fetch was not possible, that's why I used fetch node but it would go with axios anyway especially because there is a good chance that you are using it on the client. Cheers

Collapse
 
braydoncoyer profile image
Braydon Coyer

Very handy! There isn’t a lot of content around testing with Next apps; this was very informative!

Collapse
 
maciekgrzybek profile image
Maciek Grzybek

Glad I could help :)

Collapse
 
danilockthar profile image
Daniel Arroyo

You didnt specify where the test has to be located and with what name.

Collapse
 
maciekgrzybek profile image
Maciek Grzybek

It doesn't really matter :) You can name it however you want and put it wherever you want :)

Collapse
 
soudagar profile image
saifulla

Hi @maciekgrzybek nice post!!
can we use MSW to test pages/api of next js application, I am using cypress I need idea how to mock pages/api in next js application.
Thank you

Collapse
 
maciekgrzybek profile image
Maciek Grzybek

Hey, thanks for that :) Not sure if I understand you correctly, but MSW, has something that's called setupWorker - which is similar to mock server, but can be used in the browser. Maybe that will help?