API Routes Unit Testing
July 16, 2019 at 10:56pmThe Next.js community has a new home. This thread is preserved for historical purposes. The content of this conversation may be innaccurrate or out of date. Go to new community home →
API Routes Unit Testing
July 16, 2019 at 10:56pmWhat'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
July 31, 2019 at 12:22pm
Hey you can check how we test API endpoints in our integration tests.
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
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.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. oh thats dope! Can I see an example?
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 runningAugust 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' })})
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?
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?
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?
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:
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.tsimport { 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.jsAugust 6, 2020 at 1:05pm
January 13, 2021 at 2:09pm
A little late to the party, but the
next-test-api-route-handler
package (I created it!) solves this problem in an easier way I think :)// File: test/unit.test.jsimport * as endpoint from '../pages/api/your-endpoint'import { testApiHandler } from 'next-test-api-route-handler'import type { WithConfig } from '@ergodark/next-types'// Import the handler under test from the pages/api directory and respect the// Next.js config object if it's exportedconst endpoint: WithConfig<typeof Endpoint.default> = Endpoint.default;endpoint.config = Endpoint.config;await testApiHandler({requestPatcher: req => req.headers = { key: SPECIAL_API_KEY },handler: endpoint,test: async ({ fetch }) => {expect(await fetch({ method: 'GET', body: 'data' })).toStrictEqual({ json: 'response' });}});