Testing API routes sucks, especially when using TypeScript. This article covers how to unit test Next.js API routes with TypeScript using Jest and React Testing Libraries.
This writeup is for personal reference — consume at your discretion.
Why Test API Routes?
Testing API routes is important for several reasons. First, it helps you ensure that your routes work as expected and handle different scenarios gracefully. For example, you can test that your routes return the correct data, handle errors properly, and respond to different types of requests (e.g., GET, POST, PUT, DELETE).
Second, testing API routes can help you prevent bugs, errors, security issues, and performance problems in your application. By writing tests for your routes, you can catch issues early on and fix them before they become bigger problems.
Third, testing API routes can help you improve the quality of your code and make it more maintainable. By writing tests for your routes, you can document their behavior and make it easier for other developers to understand how they work.
Tools and Libraries
To test Next.js API routes with TypeScript, we will use several tools and libraries:
Jest: a JavaScript testing framework that provides a complete testing solution for JavaScript applications.
React Testing Library: a library for testing React components that provides a set of helper functions to interact with the DOM.
node-mocks-http: a library that provides mock objects for Node.js HTTP requests and responses.
dotenv: a library that loads environment variables from a
.envfile intoprocess.env.@types/node: a package that provides TypeScript definitions for Node.js built-in modules.
Run the following command in your terminal to install:
npm install --save-dev jest @testing-library/react node-mocks-http dotenv @types/node[^3^][3]After installing these tools and libraries, you need to configure Jest to work with Next.js and TypeScript. You can do this by creating a jest.config.js file in the root of your project with the following content:
module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.(js|jsx|ts|tsx)$': '<rootDir>/node_modules/babel-jest',
},
moduleNameMapper: {
'^.+\\.(css|less|scss)$': 'identity-obj-proxy',
},
}This configuration tells Jest to use jsdom as the test environment, use Babel to transform JavaScript and TypeScript files, and mock CSS modules with an identity object proxy.
Testing GET Requests
To test GET requests to a Next.js API route using the App Router, you must create a mock request object, a mock response object, and a handler function representing your route. You can use node-mocks-http to create the mock objects and pass them to your handler function.
Here is an example of how to test a GET request to an API route that returns a list of users:
// app/api/users.page.ts
import type { NextRequest } from 'next/server'
import type { NextResponse } from 'next/server'
type User = {
id: number
name: string
}
const users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]
export default function handler(req: NextRequest) {
return NextResponse.json(users)
}
// app/api/users.test.ts
import { createMocks } from 'node-mocks-http'
import handler from './users.page'
describe('/api/users', () => {
test('returns a list of users', async () => {
const { req, res } = createMocks({
method: 'GET',
})
const response = await handler(req)
expect(response.status).toBe(200)
expect(await response.json()).toEqual([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
])
})
})In this example, we use the createMocks function from node-mocks-http to create a mock request object with the method property set to 'GET' and a mock response object. We pass the mock request object to our handler function and wait for it to return a response.
After calling our handler function, we use Jest’s expect function and its matchers (e.g., toBe, toEqual) to assert the expected behavior of our route. In this case, we expect our route to return a 200 status code and a list of users.
Testing POST Requests
To test POST requests to a Next.js API route using the App Router, you need to create a mock request object with the method property set to 'POST' and the body property set to the data you want to send in the request. You also need to create a handler function that represents your route.
Here is an example of how to test a POST request to an API route that creates a new user:
// app/api/users.page.ts
import type { NextRequest } from 'next/server'
import type { NextResponse } from 'next/server'
type User = {
id: number
name: string
}
let users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]
export default function handler(req: NextRequest) {
if (req.method === 'POST') {
const newUser = req.body as User
newUser.id = users.length + 1
users.push(newUser)
return NextResponse.created(users)
} else {
return NextResponse.json(users)
}
}
// app/api/users.test.ts
import { createMocks } from 'node-mocks-http'
import handler from './users.page'
describe('/api/users', () => {
test('creates a new user', async () => {
const { req, res } = createMocks({
method: 'POST',
body: { name: 'Charlie' },
})
const response = await handler(req)
expect(response.status).toBe(201)
expect(await response.json()).toEqual([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
])
})
})In this example, we use the createMocks function from node-mocks-http to create a mock request object with the method property set to 'POST' and the body property set to an object with the name property set to 'Charlie'. We pass this object to our handler function and wait for it to return a response.
After calling our handler function, we use Jest’s expect function and its matchers (e.g., toBe, toEqual) to assert the expected behavior of our route. In this case, we expect our route to return a 201 status code and a list of users, including the new user.
Testing Other Scenarios
In addition to testing GET and POST requests, you can also test other scenarios and inputs for your API routes using the App Router. For example, you can test error handling, validation, authentication, authorization, headers, cookies, query parameters, body parameters, etc.
Here is an example of how to test error handling in an API route that returns a user by ID:
// app/api/users/[id].page.ts
import type { NextRequest } from 'next/server'
import type { NextResponse } from 'next/server'
type User = {
id: number
name: string
}
const users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]
export default function handler(req: NextRequest) {
const id = Number(req.query.id)
const user = users.find((user) => user.id === id)
if (user) {
return NextResponse.json(user)
} else {
return NextResponse.notFound({ message: 'User not found' })
}
}
// app/api/users/[id].test.ts
import { createMocks } from 'node-mocks-http'
import handler from './[id].page'
describe('/api/users/[id]', () => {
test('returns a user by ID', async () => {
const { req, res } = createMocks({
method: 'GET',
query: { id: '1' },
})
const response = await handler(req)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({ id: 1, name: 'Alice' })
})
test('returns an error if the user is not found', async () => {
const { req, res } = createMocks({
method: 'GET',
query: { id: '3' },
})
const response = await handler(req)
expect(response.status).toBe(404)
expect(await response.json()).toEqual({ message: 'User not found' })
})
})In this example, we use the createMocks function from node-mocks-http to create two sets of mock objects for our tests. The first set has a mock request object with the method property set to 'GET' and the query property set to an object with the id property set to '1'. The second set has a mock request object with the method property set to 'GET' and the queryproperty set to an object with the id property set to '3'. We pass these objects and their corresponding mock response objects to our handler function and wait for it to finish.
After calling our handler function, we use Jest’s expect function and its matchers (e.g., toBe, toEqual) to assert the expected behavior of our route1. In the first test, we expect our route to return a 200 status code and a user object with the id property set to 1. In the second test, we expect our route to return a 404 status code and an error message.
Conclusion
This article shows you how to unit test Next.js API routes with TypeScript using Jest and React Testing Library. We have covered the tools and libraries you need and examples of testing different types of requests and scenarios.
By following these steps, you can write tests for your API routes and ensure they work as expected. You can also improve the quality of your code, prevent bugs and errors, and make your application more secure and performant.
By Zach Shallbetter,
Creative Product & Technology Leader | Building Teams and Human-Centered Solutions in Spokane Washington.