menu

Next.js

A place to chat about Next.js and answer questions. For all other ZEIT products, check this out.

Channels
Team

API Routes Unit Testing

July 16, 2019 at 10:56pm

API Routes Unit Testing

July 16, 2019 at 10:56pm
What's up Fam... I was wondering how you go about writing unit tests for the new API routes feature? I am having a hard time wrapping my head around how to use Jest in this scenario.

July 31, 2019 at 2:56am
Hey Adrian, did you figure this out? I've had a lot of success with fetch-test-server on other express-based projects but unfortunately no such luck with Next :(
It seems to fail on webpack dll loading because of the hot module reloading that can't be turned off.
  • reply
  • like
Unfortunately I have not been able to figure this one out. I resorted to testing the endpoints generated by Next.js but I would rather import the methods created within api/ directory and test those instead.
  • reply
  • like
I resorted to testing the endpoints generated by Next.js
could you give an example?
  • reply
  • like

July 31, 2019 at 12:22pm
Hey you can check how we test API endpoints in our integration tests.
  • reply
  • like
I did something similar to what does in the link above. What I would like to do is something similar to what is shown in this article. https://medium.com/express-routes-a-tdd-approach-1e12a0799352
  • reply
  • like
Basically rather than testing the endpoint directly localhost:3000/api/cars, i want to import api/cars into my test file and run the tests I need to run. This will allow me to run my test without a next.js server up and running.
like-fill
2
  • reply
  • like
This approach is not possible, without building it. We provide some helper functions and middlewares what are not going to work. You could otherwise test it with just wrapping it with http server for example.
  • reply
  • like
Ah, gotcha. I ended up ejecting to a custom server setup for this, I then mount the API routes only in test environment which avoids next dev mode causing issues and works quite well
  • reply
  • like
oh thats dope! Can I see an example?
  • reply
  • like

August 1, 2019 at 6:40am
TLDR is, create an express router for your api routes (see the bottom of this page https://expressjs.com/en/guide/routing.html)
Then mount this in your custom server, like:
server.use('/api', apiRouter)
For the test environment you can create another tiny server that does not mount next, like:
import express from "express";
import TestServer from "fetch-test-server";
import api from "../server/api";
const app = express();
app.use("/api", api);
const server = new TestServer(app);
export default server;
You can then use the fetch-test-server helpers in your Jest specs and test API routes without a server running
  • reply
  • like
Yes, this works, but how I said it's going to behave differently then Next.js endpoints because of request parsing and helper functions. If you are testing basic functionality it should work fine.
  • reply
  • like

August 25, 2019 at 12:18am
Something that could be interesting to build a small library around and/or perhaps expose as a public API in the next-server package is the apiResolver() function. This looks to be the place that Next.js takes a serverless API route handler and adds middleware like res.status() and res.cookies.
I've been able to get a proof of concept together to test a small API route handler.
Take this example in pages/api/ping.js:
export default (req, res) => {
res.status(200).json({ pong: 'pong' })
}
A test could look like this to inject the Next.js API route middleware and validate that the pages/api/ping.js handler works as expected (passes with the Jest test framework):
import http from 'http'
import listen from 'test-listen'
import { apiResolver } from 'next-server/dist/server/api-utils'
import ping from 'pages/api/ping'
test('responds with 200 and { "pong": "pong" }', async () => {
let requestHandler = (req, res) => apiResolver(req, res, undefined, ping)
let server = http.createServer(requestHandler)
let url = await listen(server)
let response = await fetch(url)
let json = await response.json()
expect(response.status).toBe(200)
expect(json).toEqual({ pong: 'pong' })
})
  • reply
  • like

September 21, 2019 at 12:53am
Yes, this works, but how I said it's going to behave differently then Next.js endpoints because of request parsing and helper functions. If you are testing basic functionality it should work fine.
Are there any plans to provide something like has suggested above?
  • reply
  • like
Something that could be interesting to build a small library around and/or perhaps expose as a public API in the next-server package is the apiResolver() function. This looks to be the place that Next.js takes a serverless API route handler and adds middleware like res.status() and res.cookies.
I've been able to get a proof of concept together to test a small API route handler.
Take this example in pages/api/ping.js:
export default (req, res) => {
res.status(200).json({ pong: 'pong' })
}
A test could look like this to inject the Next.js API route middleware and validate that the pages/api/ping.js handler works as expected (passes with the Jest test framework):
import http from 'http'
import listen from 'test-listen'
import { apiResolver } from 'next-server/dist/server/api-utils'
import ping from 'pages/api/ping'
test('responds with 200 and { "pong": "pong" }', async () => {
let requestHandler = (req, res) => apiResolver(req, res, undefined, ping)
let server = http.createServer(requestHandler)
let url = await listen(server)
let response = await fetch(url)
let json = await response.json()
expect(response.status).toBe(200)
expect(json).toEqual({ pong: 'pong' })
})
Have you continued to use that method for writing handler/middleware tests?
  • reply
  • like
Your approach is working great for me. Thank you!
  • reply
  • like

September 22, 2019 at 6:51pm
I was hoping I could simplify it further and do something like:
import { IncomingMessage, ServerResponse } from "http";
import { apiResolver } from "next-server/dist/server/api-utils";
import { handler } from "../index";
test("responds 200 to authed GET", async () => {
expect.assertions(1);
let req = new IncomingMessage();
let res = new ServerResponse({ method: "GET" });
res.user = { username: "scooby" };
let response = await apiResolver(req, res, undefined, handler);
expect(response.status).toBe(200);
});
However, this does not work. It throws the following error:
TypeError: Cannot read property 'readable' of undefined
Does anyone mind heping me understand why it will not work?
Edited
  • reply
  • like

December 25, 2019 at 4:18pm
Guys, I know you got a workaround to make it works, but I've been developing a project with unit and integration tests including how to test an API through Next.JS. It follows bellow to help others get a project already configured as an example:
Specific part:
  • reply
  • like

January 7, 2020 at 11:30pm
I use node-mocks-http to run unit tests, as an example I did this test https://github.com/leosuncin/mui-next-ts/blob/master/tests/pages/api/auth/login.spec.ts is in Typescript but is not much different to Javascript one
// __tests__/pages/api/auth/login.spec.ts
import { createMocks } from 'node-mocks-http';
import loginHandler from '../../../../pages/api/auth/login';
it('should login with credentials', () => {
const { req, res } = createMocks({
method: 'POST',
body: {
username: 'admin',
password: 'Pa$$w0rd!',
},
});
loginHandler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getHeaders()).toHaveProperty(
'authorization',
expect.stringMatching(/Bearer \w+/),
);
expect(res._getData()).toBeDefined();
});
Advice: do not put your tests inside pages directory or will be served as pages by Next.js
Edited
like-fill
1
  • reply
  • like