Tests with app.request()
A test is code that sends a request to the app itself and checks the response. In Hono this needs neither a server nor a port: the app.request() method passes a request straight into the app and returns a Response. Tests are run by Vitest, a popular test runner for TypeScript.
The app apart from the server
If a test imports src/index.ts, the server starts along with it. So the app is moved into its own file. Create src/app.ts and move everything except serve() there:
// src/app.ts
import { Hono } from 'hono'
import posts from './routes/posts.js'
import auth from './routes/auth.js'
const app = new Hono()
app.route('/posts', posts)
app.route('/auth', auth)
export default appOnly the start remains in src/index.ts:
// src/index.ts
import { serve } from '@hono/node-server'
import app from './app.js'
serve({ fetch: app.fetch, port: Number(process.env.PORT ?? 3000) })The server works as before, and src/app.ts can be imported in tests.
Installing Vitest
npm install -D vitestCreate vitest.config.ts in the project root with environment variables for tests:
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
env: {
DB_FILE: ':memory:',
JWT_SECRET: 'test-secret',
},
},
})The value :memory: creates an in-memory SQLite database: it is empty on every test run and does not touch blog.db.
The first test
Create src/app.test.ts:
// src/app.test.ts
import { describe, expect, it } from 'vitest'
import app from './app.js'
describe('posts', () => {
it('returns the list', async () => {
const res = await app.request('/posts')
expect(res.status).toBe(200)
expect(await res.json()).toEqual([])
})
it('returns 404 for a missing post', async () => {
const res = await app.request('/posts/999')
expect(res.status).toBe(404)
})
})Run the tests:
npx vitestThe terminal shows two green items and the summary 2 passed. Vitest keeps watching the files and reruns the tests on changes.
A request with a body and headers
The second argument of app.request() takes the same options as fetch():
// src/app.test.ts
it('does not create a post without a token', async () => {
const res = await app.request('/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Test', body: 'Text' }),
})
expect(res.status).toBe(401)
})The test checks that the requireAuth middleware rejects a request without a token.
The test script
Add the line "test": "vitest run" to the scripts section of package.json. npm test runs the tests once and exits — that is how they are run before deployment.
Common mistake
Tests use the same blog.db database as the development server. Tests create and delete posts, a day later the "list is empty" check stops passing, and your data gets mangled. Always give tests a separate database, like :memory: in vitest.config.ts.