Testing with testClient from hono/test fails with 400 Bad Request on routes defined with @hono/zod-openapi
Hopefully I'm doing something wrong, but I don't think it is possible to use hono/test testClient or .request method on routes created with zod-openapi. Please find below simplified repro.
// routes/event.ts
import { OpenAPIHono, createRoute } from '@hono/zod-openapi';
import type { AppContext } from '../lib/context';
export const eventRoutes = new OpenAPIHono<AppContext>()
.openapi(
createRoute({
method: 'get',
path: '/',
request: {
params: z.object({
eventId: z.string(),
}),
},
responses: {
...json200Response(
z.object({
eventId: z.string()
}),
'Event ID',
),
},
}),
async (c) => {
const eventId = c.req.valid('param').eventId;
return c.json({ eventId }, 200);
},
)
.get('/testing', (c) => c.json({ ok: true }, 200));
// routes/event.test.ts
import { testClient } from 'hono/testing';
import { describe, it } from 'vitest';
import { eventRoutes } from './event.ts'
const testEnv = {
HELLO: "WORLD"
};
describe('GET /event/create-event', () => {
it('Testing routes', async ({ expect }) => {
const successRes = await testClient(eventRoutes, testEnv)['testing'].$get();
console.log('success res: ', successRes)
// this passes
expect(successRes.status).toBe(200)
const failRes = await testClient(eventRoutes, testEnv)['index'].$get({ param: { eventId: '123' }});
console.log('fail res: ', failRes)
// fails with 400 Bad Request when accessing routes defined by openapi and createRoute
expect(failRes.status).toBe(200);
});
})
Logs:
Did you ever end up finding something here?
I am dealing with this exact issue right now. Is anyone else having issue passing a param to a get route using @hono/zod-openapi? I am using vitest for the unit test framework.
I am able to execute the get users by id in the swagger test harness so I know the router is setup and working correctly so it has something to do with hono/testing im assuming.
import { testClient } from "hono/testing"
import { describe, expect, expectTypeOf, it } from "vitest"
import createApp from "@/lib/createApp"
import router from "@/routes/users/users.index"
const client = testClient(createApp().route("/", router))
describe("user routes", () => {
// This works
it("GET /users list all users", async () => {
const response = await client.users.$get();
expect(response.status).toBe(200);
if (response.status === 200) {
const json = await response.json();
expectTypeOf(json).toBeArray();
expect(json.length).toBe(3);
}
});
// This does not work
it("GET /users/:id get a single user", async () => {
const id = "1";
const response = await client.users[":id"].$get({
param: {
id,
},
});
expect(response.status).toBe(200);
if (response.status === 200) {
const json = await response.json();
expect(json.id).toBe(id);
}
})
})
Here is the error details
AssertionError: expected 400 to be 200 // Object.is equality expect(response.status).toBe(200);
The following works well. I think it's not a bug.
import { OpenAPIHono, createRoute } from '@hono/zod-openapi'
import { z } from 'zod'
export const eventRoutes = new OpenAPIHono().openapi(
createRoute({
method: 'get',
path: '/users/:id',
request: {
params: z.object({
id: z.string(),
}),
},
responses: {
200: {
content: {
'application/json': {
schema: z.object({
foo: z.string(),
}),
},
},
description: 'Foo',
},
},
}),
(c) => {
return c.json({ foo: 'foo' }, 200)
}
)
import { testClient } from 'hono/testing'
const res = await testClient(eventRoutes)['users'][':id'].$get({
param: {
id: '123',
},
})
console.log(res.status) // 200 - correct
I am seeing similar error. $get is successful using the test client but $post fails with 400.
To give a bit more context:
I was trying to solve this: https://github.com/honojs/hono/issues/1677 (one of the handler calling another handler)
Inside the top handler I initialized a client using testClient
- First
$getcall ✅ - Then I do a do a
$deletecall which is also ✅ - Then I do a
$postcall which invokes the app but then gets rejected (400).
I have spent so many hours trying to debug which handler is rejecting this request. I tried reproducing in a smaller app but unfortunately I could not.
UPDATE: After debugging turns out the headers were getting passed incorrectly.
So while making the request using testClient I was passing the headers that I get from c.req like
{ headers: { ...c.req.raw.headers } }
And that was not correct.
The following using $post is working correctly. Expected. I'll close this issue. If you still have a problem, please create a new issue.
import { OpenAPIHono, createRoute } from '@hono/zod-openapi'
import { testClient } from 'hono/testing'
import { z } from 'zod'
export const eventRoutes = new OpenAPIHono().openapi(
createRoute({
method: 'post',
path: '/posts',
request: {
body: {
content: {
'application/json': {
schema: z.object({
title: z.string(),
}),
},
},
},
},
responses: {
200: {
content: {
'application/json': {
schema: z.object({
foo: z.string(),
}),
},
},
description: 'Foo',
},
},
}),
(c) => {
return c.json({ foo: 'foo' }, 200)
}
)
const res = await testClient(eventRoutes)['posts'].$post({
json: {
title: 'abc',
},
})
console.log(res.status) // 200 - correct