hono icon indicating copy to clipboard operation
hono copied to clipboard

Add Authorization header when testing using request

Open ikrom opened this issue 1 year ago • 8 comments

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

code

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

ikrom avatar Sep 20 '24 16:09 ikrom

Hi @ikrom

I can't reproduce it. Please provide a minimal project to reproduce it.

yusukebe avatar Sep 21 '24 09:09 yusukebe

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 image

Could you help how to create unit test with authorization header jwt?

ikrom avatar Sep 23 '24 01:09 ikrom

@ikrom

You should access /v1/home:

const res = await app.request(
  '/v1/home',
  {
    headers: {
      authorization: 'Bearer token',
    },
  },//...
)

yusukebe avatar Sep 23 '24 04:09 yusukebe

thank you for the correction @yusukebe image but there is another error, the res is undefined

ikrom avatar Sep 23 '24 08:09 ikrom

@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

yusukebe avatar Sep 23 '24 09:09 yusukebe

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

ikrom avatar Sep 23 '24 09:09 ikrom

should the const app = new Hono() in the same file with unit test?

No.

yusukebe avatar Sep 23 '24 09:09 yusukebe

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' })
    })
})

gerhardcit avatar Oct 02 '24 14:10 gerhardcit

This issue has been marked as stale due to inactivity.

github-actions[bot] avatar Nov 03 '24 00:11 github-actions[bot]

Closing this issue due to inactivity.

github-actions[bot] avatar Nov 06 '24 00:11 github-actions[bot]

@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])
    })
  })
})

Soviut avatar Nov 28 '24 22:11 Soviut

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 avatar Nov 22 '25 11:11 hdformat

@hdformat This is a closed issue, you should open a new issue if you think there are mistakes in the docs.

Soviut avatar Nov 22 '25 11:11 Soviut