Add Authorization header when testing using request
What version of Hono are you using?
4.5.11
What runtime/platform is your app running on?
cloudflare worker + bun
What steps can reproduce the bug?
when I want to create unit test using app.request, I can't add authorization header to check JWT is valid or not
What is the expected behavior?
It should not error and unit test can run successfully
What do you see instead?
unit test error and can't run it
Additional information
No response
Hi @ikrom
I can't reproduce it. Please provide a minimal project to reproduce it.
Hi @yusukebe
Actually I want to create unit testing, I have app with route /home
import { Hono } from "hono";
const app = new Hono<{ Bindings: Bindings }>().basePath("/v1");
app.get("/home", async (c) => {
return c.json({ env: c.env.ENVIRONMENT, message: "Hello World!" });
});
Then I create file home.test.ts to test the route /home
import { describe, test, expect } from "bun:test";
import { Bindings, MOCK_ENV } from "@type";
describe("Index Test", () => {
test("GET /home", async () => {
const res = await app.request(
"/home",
{
headers: {
authorization: `Bearer token`,
},
},
MOCK_ENV
);
expect(res.status).toBe(200);
});
});
But the result is like picture below
Could you help how to create unit test with authorization header jwt?
@ikrom
You should access /v1/home:
const res = await app.request(
'/v1/home',
{
headers: {
authorization: 'Bearer token',
},
},//...
)
thank you for the correction @yusukebe
but there is another error, the res is undefined
@ikrom
I don't know, but the following code is working well:
const app = new Hono().basePath('/v1')
app.get('/home', async (c) => {
return c.json({})
})
const res = await app.request(
'/v1/home',
{
headers: {
authorization: 'Bearer token',
},
} //...
)
console.log(res.status) // 2000
should the const app = new Hono() in the same file with unit test? because the unit test can't run if the app in different file with unit test
should the
const app = new Hono()in the same file with unit test?
No.
Not sure if this helps, but I had to do a fallback in middleware.ts for env to work in production as well in tests.
app.ts
import { Hono } from "hono";
import { configureAppMiddleware } from "./middleware";
import { configureAppRoutes } from "./router";
export function getHonoApp() {
const app = new Hono();
configureAppMiddleware(app);
configureAppRoutes(app);
return app;
}
middleware.ts
import { Hono } from "hono";
import { env } from "hono/adapter";
import { bearerAuth } from "hono/bearer-auth";
export const configureAppMiddleware = (app: Hono) => {
app.use('/api/*', bearerAuth({
verifyToken: async (token, c) => {
// this works in runtime/production with the hono/adapter as per hono docs
let { API_KEY } = env<{ API_KEY: string }>(c);
// in testing when MOCK_ENV is passed in, we need to do this as a fallback
if (!API_KEY) {
API_KEY = c.env.API_KEY;
}
return token === API_KEY
}
}))
}
the test looks like this:
app.authed.test.ts
import { beforeAll, describe, expect, test } from "vitest";
import { getHonoApp } from "./app";
const MOCK_ENV = {
API_KEY: 'test_api_key',
}
describe('App - authed routes', () => {
let app = getHonoApp();
let headers: Record<string, string>;
beforeAll(() => {
headers = {
"Content-Type": "application/json",
"Authorization": `Bearer ${MOCK_ENV.API_KEY}`
}
});
test('should return 200 and You are authorized on /api/page', async () => {
// there is a /api/page route that returns some json
const res = await app.request('/api/page', { headers }, MOCK_ENV);
// this succeed because of the fallback (hack?) in middleware.ts
expect(res.status).toBe(200);
expect(await res.json()).toEqual({ message: 'You are authorized' })
})
})
This issue has been marked as stale due to inactivity.
Closing this issue due to inactivity.
@gerhardcit thanks for this example. It's what finally clarified the approach I should take for swapping middleware during tests.
I now have a createApp(db?) function that takes an optional db client. If it's present, override the normal database middleware before returning the app instance.
Here's some code for posterity.
export function createApp(db?) {
const app = new Hono()
if (db) {
app.use((c, next) => {
c.set('db', db)
return next()
})
} else {
app.use(databaseMiddleware)
}
return app
}
const appPostgres = createApp() // this one for live
const appPGlite = createApp(drizzle(new PGlite())) // this one for tests
Then in my tests I can do the following before each test
describe('/testing', () => {
let db: Awaited<ReturnType<typeof createTestDb>>
let app: ReturnType<typeof createApp>
beforeEach(async () => {
db = await createTestDb()
app = createApp(db)
})
describe('GET /profiles', () => {
it('should return a profile', async () => {
// I can manipulate the database using the client
const profile = db.insert(profiles).values({ name: 'profile name' }).returning()
// then I can make the request from the app that is using the same client middleware
const res = await app.request('/profiles')
expect(await res.json()).toStrictEqual([profile])
})
})
})
the official testing document is saying like below
const res = await app.request('/posts', {
method: 'POST',
body: JSON.stringify({ message: 'hello hono' }),
headers: new Headers({ 'Content-Type': 'application/json' }),
})
but when i write my testing like this
const res = await app.request('/posts', {
method: 'POST',
body: JSON.stringify({ message: 'hello hono' }),
headers: new Headers({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ...' }),
})
the middleware says invalid credential structure, it seems that the headers are interpreted in wrong way
{
authorization: 'Bearer ...',
'content-type': 'application/json'
}
so, i should rewrite my testing like this to pass the testing.
const res = await app.request('/posts', {
method: 'POST',
body: JSON.stringify({ message: 'hello hono' }),
headers: {contentType: 'application/json', authorization: 'Bearer ...' }),
})
@hdformat This is a closed issue, you should open a new issue if you think there are mistakes in the docs.