I encountered an issue during tests launch for Next.js API call. It's just JWT authentication request with Next and Jose library. I can't recognize a response object to check its body, status and headers at least. Thus, the problem is to get correct request
argument into Jest environment to test the request as it's working well into browser and Postman
I got route.js API route file:
import { SignJWT } from "jose";
import { NextResponse } from "next/server";
import { getJwtSecretKey } from "@/libs/auth";
export async function POST(request) {
try {
const body = await request.json();
if (body.username === "test-name" && body.password === "password1234") {
const token = await new SignJWT({
username: body.username,
})
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("30s")
.sign(getJwtSecretKey());
const response = NextResponse.json(
{ success: true },
{ status: 200, headers: { "content-type": "application/json" } }
);
response.cookies.set({
name: "token",
value: token,
path: "/",
});
// Temporary stub for user profile
response.cookies.set({
name: "user-name",
value: body.username,
path: "/api",
});
return response;
}
return NextResponse.json(
{ success: false },
{ status: 401, headers: { "content-type": "application/json" } }
);
} catch (error) {
return console.error(error)
}
}
I'm using console.error
here to expose exact exception to console
The test file is:
import request from 'supertest';
import { POST } from './route';
import { getJwtSecretKey } from '../../../libs/auth';
import * as Jose from "jose";
jest.mock('../../../libs/auth', () => ({
getJwtSecretKey: jest.fn(),
}));
jest.mock('next/dist/server/web/exports/next-response.js', () => ({ Request: jest.fn() }));
jest.mock('jose', () => ({
SignJWT: jest.fn(() => Jose.SignJWT)
}));
describe('POST API Endpoint', () => {
it('should return a success response with a token for valid credentials', async () => {
jest.useFakeTimers('legacy');
getJwtSecretKey.mockReturnValue('your-secret-key');
const validCredentials = {
username: 'test-name',
password: 'password1234',
};
const response = await request(POST)
.post('/api/login')
.set('Content-Type', 'application/json')
.send(JSON.stringify(validCredentials));
;
expect(response.status).toBe(200);
expect(response.body).toEqual({ success: true });
expect(response.headers['content-type']).toBe('application/json');
expect(response.header['set-cookie']).toContain('token=');
});
// Other test cases here
});
The test result is:
console.error
TypeError: request.json is not a function
at Server.json (C:\Users\smiss-dev\projects\auth-jwt\src\app\api\login\route.js:7:36)
at Server.emit (node:events:517:28)
at parserOnIncoming (node:_http_server:1107:12)
at HTTPParser.parserOnHeadersComplete (node:_http_common:119:17)
33 | return NextResponse.json({ success: false });
34 | } catch (error) {
> 35 | console.error(error)
| ^
36 | }
37 | }
at Server.error (src/app/api/login/route.js:35:17)
FAIL src/app/api/login/route.test.js (5.487 s)
POST API Endpoint
× should return a success response with a token for valid credentials (5002 ms)
● POST API Endpoint › should return a success response with a token for valid credentials
thrown: "Exceeded timeout of 5000 ms for a test.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."
15 |
16 | describe('POST API Endpoint', () => {
> 17 | it('should return a success response with a token for valid credentials', async () => {
| ^
18 | jest.useFakeTimers('legacy');
19 | getJwtSecretKey.mockReturnValue('your-secret-key');
20 |
at it (src/app/api/login/route.test.js:17:5)
at Object.describe (src/app/api/login/route.test.js:16:1)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 5.911 s, estimated 6 s
Ran all test suites matching /route/i.
Jest did not exit one second after the test run has completed.
It looks that request is falling into catch
block and request object does not consist json
, hence the request has timeout and the test is not completed
The first of all I've checked the request
object and I found out that the body also absent here:
const body = await request.body
exposes undefined
.
Also the request is not NextRequest
object.
I've applied "http" library to test the request:
import request from 'supertest';
import { POST } from './route';
import { getJwtSecretKey } from '../../../libs/auth';
import { createServer } from "http";
import * as Jose from "jose";
jest.mock('../../../libs/auth', () => ({
getJwtSecretKey: jest.fn(),
}));
jest.mock('next/dist/server/web/exports/next-response.js', () => ({ Request: jest.fn() }));
jest.mock('jose', () => ({
SignJWT: jest.fn(() => Jose.SignJWT)
}));
describe('POST API Endpoint', () => {
let server;
beforeAll(() => {
server = createServer(POST);
server.listen();
});
afterAll((done) => {
server.close(done);
});
it('should return a success response with a token for valid credentials', async () => {
jest.useFakeTimers('legacy');
getJwtSecretKey.mockReturnValue('your-secret-key');
const validCredentials = {
username: 'test-name',
password: 'password1234',
};
const response = await request(server)
.post('/api/login')
.set('Content-Type', 'application/json')
.send(JSON.stringify(validCredentials));
;
expect(response.status).toBe(200);
expect(response.body).toEqual({ success: true });
expect(response.headers['content-type']).toBe('application/json');
expect(response.header['set-cookie']).toContain('token=');
});
// Other tests here
});
Result is the same
jest.config.js:
const nextJest = require('next/jest')
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
})
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
}
module.exports = createJestConfig(customJestConfig)