Randomness
in Code

Testing APIs in Next.js

February 2, 2024

The latest version of Next.js (which uses the App Router) allows developers to define APIs using Route Handlers. APIs are then defined by the location of the route.js file (which defines the URL) and the exported function (which defines the HTTP method: GET, POST, etc). Documentation exists for creating the routes, but how to test those routes is left to the user.

In previous Next.js iterations, I had used node-mocks-http. But the App Router updates makes that solution no longer an option. Instead I utilized a mixture of directly creating Request objects and mocking any downstream dependencies with Jest.

Creating a Request

In the test, to create the request I wrapped Request with NextRequest. For example, to send a request to test /api/widgets:

const req = new NextRequest(new Request('http://domain/api/widgets'), {
  method: 'GET',
});
const res = await GET(req);

Note that the HTTP protocol and the domain do not matter here, but need to be supplied to fullfil the requirement of the request/response processing. So simply putting http://domain will suffice.

Running this test will send an empty GET request to /api/widgets and set the response in res. You could then verify the response data with something like:

expect(res.status).toEqual(200);

Mocking the Downstream Dependencies

If your API makes database calls, you may want to mock those calls or provide stubs, or a combination. In this example we'll use Jest to mock the Next.js Server Action function which loads the widgets, named getWidgets.

Imagine this is the API code:

export async function GET(request: NextRequest) {
  const response = await getWidgets();

  return Response.json(response);
}

In your test, you could mock getWidgets like so:

const mockGetWidgets = jest.fn();
jest.mock('<path_to_actions>/actions/widgets', () => ({
  getWidgets: (...args: unknown[]) => mockGetWidgets(...args),
}));

Then in the test, let's have the widgets return a list of strings: ["hello", "you"]. Our full test would then be:

mockGetWidgets.mockReturnValue(['hello', 'you']);

const req = new NextRequest(new Request('http://domain/api/widgets'), {
  method: 'GET',
});
const res = await GET(req);

expect(mockGetWidgets).toHaveBeenCalled();
expect(res.status).toEqual(200);
expect(await res.json()).toEqual(['hello', 'you']);