supertest
supertest copied to clipboard
How can I test dynamic next.js API route using supertest in an integration test scenario?
I have this simple example of testing an API route using supertest. I want to do an "integration test". So I want to hit the actual API, execute all the code in the API handler and test the result.
It works fine until I try to read a query parameter in a dynamic API route.
Repo: https://github.com/riha/test-supertest
Next.js dynamic API /api/[name].ts
import type { NextApiRequest, NextApiResponse } from "next";
type Data = {
name: string;
};
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
console.log(req);
// nothing get inserted into req.query 🥺
const name = req.query.name as string;
res.status(200).json({ name: name });
}
And the test __tests__/api/name.ts
const testClient = (handler: NextApiHandler) => {
const listener: RequestListener = (req, res) => {
return apiResolver(
req,
res,
undefined,
handler,
{
previewModeEncryptionKey: "",
previewModeId: "",
previewModeSigningKey: "",
},
false
);
};
return request(createServer(listener));
};
it("What's my name!?", async () => {
const client = testClient(handler);
const response = await client.get("/api/name").query({ name: "John" });
console.log(response.body);
expect(true);
});
Test API gets called but the query parameter isn't parsed into the request object. So req.query
is always undefined
. I'm sure this is obvious to some? I guess I can't call the handler this way and that I somehow miss some next.js magic middleware?
But how can I do it differently? Could I start a server and a port listener and call it using that somehow? Could I somehow create the handler differently? Is there a way where I could also add the data to the request param object, before sending it, I guess that could work as well?
You can pass query parameters to your api end-point by specifying them as third parameter of the apiResolver
:
const testClient = (handler: NextApiHandler) => {
const listener: RequestListener = (req, res) => {
return apiResolver(
req,
res,
{ name: "John" }, // Query params
handler,
{
previewModeEncryptionKey: "",
previewModeId: "",
previewModeSigningKey: "",
},
false
);
};
return request(createServer(listener));
};
You can then change your test code to:
it("What's my name!?", async () => {
const client = testClient(handler);
const response = await client.get("/api/name");
console.log(response.body);
expect(true);
});
You can find the declaration of the apiResolver
here.
Hey, was anybody able to come to the bottom of this 🤔
Since we are passing handler
"directly" it doesn't matter which endpoint url gets passed
const response = await client.get("/api/name-slug");
where query params should be passed to actual request getting called client.get("/api/name").query({param1: 'testValue'})
(not passing/hardcoding it to testClient
)
Still have yet to completely solve this issue, but passing the query to testClient seems like a valid workaround.
Update your testClient with the following:
export const queryClient = (handler: NextApiHandler, query: object) => {
const listener: RequestListener = (req, res) => {
return apiResolver(
req,
res,
query,
handler,
{
previewModeEncryptionKey: '',
previewModeId: '',
previewModeSigningKey: '',
},
false
);
};
return request(createServer(listener));
};
and you can fulfill the request with such:
it('should GET and return a 200 status code', async () => {
const res = await queryClient(getUserIdHandler, { id: 'hi' }).get('/');
expect(res.status).toEqual(200);
});
Happy coding :>
Still have yet to completely solve this issue, but passing the query to testClient seems like a valid workaround.
Update your testClient with the following:
export const queryClient = (handler: NextApiHandler, query: object) => { const listener: RequestListener = (req, res) => { return apiResolver( req, res, query, handler, { previewModeEncryptionKey: '', previewModeId: '', previewModeSigningKey: '', }, false ); }; return request(createServer(listener)); };
and you can fulfill the request with such:
it('should GET and return a 200 status code', async () => { const res = await queryClient(getUserIdHandler, { id: 'hi' }).get('/'); expect(res.status).toEqual(200); });
Happy coding :>
The problem with this method is that you need to create an http instance for each handler, and it is not matching with the URL you're giving to supertest. It will work either with .get('/toto') or with .get('/abcd').
But don't worry, I've just released an npm package which handle this case: https://www.npmjs.com/package/nextjs-http-supertest
Feel free to use it and give me feedback !
Hey, was anybody able to come to the bottom of this 🤔 Since we are passing
handler
"directly" it doesn't matter which endpoint url gets passedconst response = await client.get("/api/name-slug");
where query params should be passed to actual request getting calledclient.get("/api/name").query({param1: 'testValue'})
(not passing/hardcoding it totestClient
)
Yep I did, just take a look at my reply just above 👍 https://www.npmjs.com/package/nextjs-http-supertest
Hey, was anybody able to come to the bottom of this thinking Since we are passing
handler
"directly" it doesn't matter which endpoint url gets passedconst response = await client.get("/api/name-slug");
where query params should be passed to actual request getting calledclient.get("/api/name").query({param1: 'testValue'})
(not passing/hardcoding it totestClient
)Yep I did, just take a look at my reply just above +1 https://www.npmjs.com/package/nextjs-http-supertest
Doesn't work for me :(
And If I try create the server manually as the first response, I have problems with the env variables, but if I test with Postman everything work
Hey, was anybody able to come to the bottom of this thinking Since we are passing
handler
"directly" it doesn't matter which endpoint url gets passedconst response = await client.get("/api/name-slug");
where query params should be passed to actual request getting calledclient.get("/api/name").query({param1: 'testValue'})
(not passing/hardcoding it totestClient
)Yep I did, just take a look at my reply just above +1 https://www.npmjs.com/package/nextjs-http-supertest
Doesn't work for me :(
And If I try create the server manually as the first response, I have problems with the env variables, but if I test with Postman everything work
Can you please provide more code about how did you init your server ? (The best should be a link to your github repo)
Hey, was anybody able to come to the bottom of this thinking Since we are passing
handler
"directly" it doesn't matter which endpoint url gets passedconst response = await client.get("/api/name-slug");
where query params should be passed to actual request getting calledclient.get("/api/name").query({param1: 'testValue'})
(not passing/hardcoding it totestClient
)Yep I did, just take a look at my reply just above +1 https://www.npmjs.com/package/nextjs-http-supertest
Doesn't work for me :(
And If I try create the server manually as the first response, I have problems with the env variables, but if I test with Postman everything work
Can you please provide more code about how did you init your server ? (The best should be a link to your github repo)
Yeah, of course. Not up to date but basically I import de package on the test file. Repo: https://github.com/LksR-dev/crud-nextjs-ts
Hi,
I am trying to test my NextJS APIs using your approach. It seems to be return,
`● Test suite failed to run
ReferenceError: Headers is not defined
at Object.<anonymous> (node_modules/next/dist/server/web/spec-extension/adapters/headers.js:32:30)
at Object.<anonymous> (node_modules/next/dist/server/api-utils/index.js:67:18)`
Does anyone know how to fix this or why this error is showing up in the first place. My Next.js version is 13.3.4.
Another question I have is, when not using your approach, it almost works expect in one of my APIs I am redirecting to another localhost API which handles some specific logic.
example: From http://localhost:3000/api/posts
(which is the handler I am targeting in my tests) I am calling http://localhost:3000/api/formatPosts
and this seems to return a ECONNREFUSED error. I thought supertest was supposed to be running a temporary server for my API tests but it doesn't seem to be. Please help with this approach as well