📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -0,0 +1,738 @@
|
||||
# Testing Patterns
|
||||
|
||||
Type-safe testing with `testClient` — no HTTP server required.
|
||||
|
||||
## Basic Setup
|
||||
|
||||
```typescript
|
||||
import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
|
||||
import { testClient } from 'hono/testing';
|
||||
import { Hono } from 'hono';
|
||||
import { Database } from 'bun:sqlite';
|
||||
|
||||
// Simple app for testing
|
||||
const createApp = () => {
|
||||
return new Hono()
|
||||
.get('/health', (c) => c.json({ status: 'ok' }))
|
||||
.get('/error', () => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
};
|
||||
|
||||
describe('Basic Tests', () => {
|
||||
test('GET /health returns 200', async () => {
|
||||
const app = createApp();
|
||||
const client = testClient(app);
|
||||
|
||||
const res = await client.health.$get();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data).toEqual({ status: 'ok' });
|
||||
});
|
||||
|
||||
test('GET /error returns 500', async () => {
|
||||
const app = createApp();
|
||||
const client = testClient(app);
|
||||
|
||||
const res = await client.error.$get();
|
||||
|
||||
expect(res.status).toBe(500);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Testing CRUD Operations
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
|
||||
const CreatePostSchema = z.object({
|
||||
title: z.string().min(1).max(200),
|
||||
content: z.string().min(1),
|
||||
});
|
||||
|
||||
describe('Posts API', () => {
|
||||
let db: Database;
|
||||
let app: Hono;
|
||||
let client: ReturnType<typeof testClient>;
|
||||
|
||||
beforeEach(() => {
|
||||
// In-memory database for each test
|
||||
db = new Database(':memory:');
|
||||
db.run(`
|
||||
CREATE TABLE posts (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
// Create app with database
|
||||
app = new Hono()
|
||||
.get('/posts', (c) => {
|
||||
const posts = db.query('SELECT * FROM posts').all();
|
||||
return c.json({ posts });
|
||||
})
|
||||
.get('/posts/:id', (c) => {
|
||||
const post = db.query('SELECT * FROM posts WHERE id = ?').get(c.req.param('id'));
|
||||
if (!post) {
|
||||
throw new HTTPException(404, { message: 'Post not found' });
|
||||
}
|
||||
return c.json({ post });
|
||||
})
|
||||
.post('/posts', zValidator('json', CreatePostSchema), (c) => {
|
||||
const data = c.req.valid('json');
|
||||
const post = db.query(
|
||||
'INSERT INTO posts (id, title, content) VALUES (?, ?, ?) RETURNING *'
|
||||
).get(crypto.randomUUID(), data.title, data.content);
|
||||
return c.json({ post }, 201);
|
||||
})
|
||||
.delete('/posts/:id', (c) => {
|
||||
const post = db.query('DELETE FROM posts WHERE id = ? RETURNING *').get(c.req.param('id'));
|
||||
if (!post) {
|
||||
throw new HTTPException(404, { message: 'Post not found' });
|
||||
}
|
||||
return c.json({ deleted: true, post });
|
||||
});
|
||||
|
||||
client = testClient(app);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
db.close();
|
||||
});
|
||||
|
||||
test('GET /posts returns empty array initially', async () => {
|
||||
const res = await client.posts.$get();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.posts).toEqual([]);
|
||||
});
|
||||
|
||||
test('POST /posts creates post', async () => {
|
||||
const res = await client.posts.$post({
|
||||
json: {
|
||||
title: 'Test Post',
|
||||
content: 'This is a test post',
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.post).toMatchObject({
|
||||
title: 'Test Post',
|
||||
content: 'This is a test post',
|
||||
});
|
||||
expect(data.post.id).toBeTruthy();
|
||||
expect(data.post.created_at).toBeTruthy();
|
||||
});
|
||||
|
||||
test('POST /posts validates input', async () => {
|
||||
const res = await client.posts.$post({
|
||||
json: { title: '' } as any // Invalid input
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
|
||||
const error = await res.json();
|
||||
expect(error).toHaveProperty('error');
|
||||
});
|
||||
|
||||
test('GET /posts/:id returns post', async () => {
|
||||
// Create post first
|
||||
const createRes = await client.posts.$post({
|
||||
json: { title: 'Test', content: 'Content' }
|
||||
});
|
||||
const { post } = await createRes.json();
|
||||
|
||||
// Get post
|
||||
const res = await client.posts[':id'].$get({
|
||||
param: { id: post.id }
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.post).toEqual(post);
|
||||
});
|
||||
|
||||
test('GET /posts/:id returns 404 for non-existent post', async () => {
|
||||
const res = await client.posts[':id'].$get({
|
||||
param: { id: 'non-existent' }
|
||||
});
|
||||
|
||||
expect(res.status).toBe(404);
|
||||
|
||||
const error = await res.json();
|
||||
expect(error.error).toBe('Post not found');
|
||||
});
|
||||
|
||||
test('DELETE /posts/:id deletes post', async () => {
|
||||
// Create post
|
||||
const createRes = await client.posts.$post({
|
||||
json: { title: 'Test', content: 'Content' }
|
||||
});
|
||||
const { post } = await createRes.json();
|
||||
|
||||
// Delete post
|
||||
const deleteRes = await client.posts[':id'].$delete({
|
||||
param: { id: post.id }
|
||||
});
|
||||
|
||||
expect(deleteRes.status).toBe(200);
|
||||
|
||||
const deleteData = await deleteRes.json();
|
||||
expect(deleteData.deleted).toBe(true);
|
||||
|
||||
// Verify deletion
|
||||
const getRes = await client.posts[':id'].$get({
|
||||
param: { id: post.id }
|
||||
});
|
||||
|
||||
expect(getRes.status).toBe(404);
|
||||
});
|
||||
|
||||
test('GET /posts returns created posts', async () => {
|
||||
// Create multiple posts
|
||||
await client.posts.$post({ json: { title: 'Post 1', content: 'Content 1' } });
|
||||
await client.posts.$post({ json: { title: 'Post 2', content: 'Content 2' } });
|
||||
await client.posts.$post({ json: { title: 'Post 3', content: 'Content 3' } });
|
||||
|
||||
const res = await client.posts.$get();
|
||||
const data = await res.json();
|
||||
|
||||
expect(data.posts).toHaveLength(3);
|
||||
expect(data.posts.map((p: any) => p.title)).toEqual(['Post 1', 'Post 2', 'Post 3']);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Testing Authentication
|
||||
|
||||
```typescript
|
||||
import { createFactory } from 'hono/factory';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
type Env = {
|
||||
Variables: {
|
||||
user: { id: string; role: 'admin' | 'user' };
|
||||
};
|
||||
};
|
||||
|
||||
const factory = createFactory<Env>();
|
||||
|
||||
// Mock token verification
|
||||
const mockUsers = new Map([
|
||||
['valid-token', { id: 'user-123', role: 'user' as const }],
|
||||
['admin-token', { id: 'admin-456', role: 'admin' as const }],
|
||||
]);
|
||||
|
||||
const authMiddleware = factory.createMiddleware(async (c, next) => {
|
||||
const token = c.req.header('authorization')?.replace('Bearer ', '');
|
||||
|
||||
if (!token) {
|
||||
throw new HTTPException(401, { message: 'Missing token' });
|
||||
}
|
||||
|
||||
const user = mockUsers.get(token);
|
||||
|
||||
if (!user) {
|
||||
throw new HTTPException(401, { message: 'Invalid token' });
|
||||
}
|
||||
|
||||
c.set('user', user);
|
||||
await next();
|
||||
});
|
||||
|
||||
const requireAdmin = factory.createMiddleware(async (c, next) => {
|
||||
const user = c.get('user');
|
||||
|
||||
if (user.role !== 'admin') {
|
||||
throw new HTTPException(403, { message: 'Admin access required' });
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
|
||||
describe('Authentication', () => {
|
||||
const app = factory.createApp()
|
||||
.get('/public', (c) => c.json({ public: true }))
|
||||
.use('/protected/*', authMiddleware)
|
||||
.get('/protected/profile', (c) => {
|
||||
const user = c.get('user');
|
||||
return c.json({ user });
|
||||
})
|
||||
.use('/protected/admin/*', requireAdmin)
|
||||
.get('/protected/admin/dashboard', (c) => {
|
||||
return c.json({ admin: true });
|
||||
});
|
||||
|
||||
const client = testClient(app);
|
||||
|
||||
test('Public route accessible without auth', async () => {
|
||||
const res = await client.public.$get();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.public).toBe(true);
|
||||
});
|
||||
|
||||
test('Protected route requires auth', async () => {
|
||||
const res = await client.protected.profile.$get();
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
|
||||
const error = await res.json();
|
||||
expect(error.error).toBe('Missing token');
|
||||
});
|
||||
|
||||
test('Protected route accepts valid token', async () => {
|
||||
const res = await client.protected.profile.$get({}, {
|
||||
headers: { Authorization: 'Bearer valid-token' }
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.user).toEqual({ id: 'user-123', role: 'user' });
|
||||
});
|
||||
|
||||
test('Protected route rejects invalid token', async () => {
|
||||
const res = await client.protected.profile.$get({}, {
|
||||
headers: { Authorization: 'Bearer invalid-token' }
|
||||
});
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
|
||||
const error = await res.json();
|
||||
expect(error.error).toBe('Invalid token');
|
||||
});
|
||||
|
||||
test('Admin route requires admin role', async () => {
|
||||
const res = await client.protected.admin.dashboard.$get({}, {
|
||||
headers: { Authorization: 'Bearer valid-token' }
|
||||
});
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
|
||||
const error = await res.json();
|
||||
expect(error.error).toBe('Admin access required');
|
||||
});
|
||||
|
||||
test('Admin route accepts admin token', async () => {
|
||||
const res = await client.protected.admin.dashboard.$get({}, {
|
||||
headers: { Authorization: 'Bearer admin-token' }
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const data = await res.json();
|
||||
expect(data.admin).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Testing Middleware
|
||||
|
||||
```typescript
|
||||
import { logger } from 'hono/logger';
|
||||
|
||||
describe('Middleware', () => {
|
||||
test('Logger middleware logs requests', async () => {
|
||||
const logs: string[] = [];
|
||||
|
||||
// Custom logger that captures logs
|
||||
const customLogger = (message: string) => {
|
||||
logs.push(message);
|
||||
};
|
||||
|
||||
const app = new Hono()
|
||||
.use('*', logger(customLogger))
|
||||
.get('/test', (c) => c.json({ ok: true }));
|
||||
|
||||
const client = testClient(app);
|
||||
|
||||
await client.test.$get();
|
||||
|
||||
expect(logs.length).toBeGreaterThan(0);
|
||||
expect(logs.some(log => log.includes('GET'))).toBe(true);
|
||||
expect(logs.some(log => log.includes('/test'))).toBe(true);
|
||||
});
|
||||
|
||||
test('Custom middleware runs before handler', async () => {
|
||||
const executionOrder: string[] = [];
|
||||
|
||||
const app = new Hono()
|
||||
.use('*', async (c, next) => {
|
||||
executionOrder.push('middleware-before');
|
||||
await next();
|
||||
executionOrder.push('middleware-after');
|
||||
})
|
||||
.get('/test', (c) => {
|
||||
executionOrder.push('handler');
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
|
||||
const client = testClient(app);
|
||||
|
||||
await client.test.$get();
|
||||
|
||||
expect(executionOrder).toEqual([
|
||||
'middleware-before',
|
||||
'handler',
|
||||
'middleware-after',
|
||||
]);
|
||||
});
|
||||
|
||||
test('Middleware can modify context', async () => {
|
||||
type Env = {
|
||||
Variables: {
|
||||
requestId: string;
|
||||
timestamp: number;
|
||||
};
|
||||
};
|
||||
|
||||
const factory = createFactory<Env>();
|
||||
|
||||
const contextMiddleware = factory.createMiddleware(async (c, next) => {
|
||||
c.set('requestId', crypto.randomUUID());
|
||||
c.set('timestamp', Date.now());
|
||||
await next();
|
||||
});
|
||||
|
||||
const app = factory.createApp()
|
||||
.use('*', contextMiddleware)
|
||||
.get('/test', (c) => {
|
||||
return c.json({
|
||||
requestId: c.get('requestId'),
|
||||
timestamp: c.get('timestamp'),
|
||||
});
|
||||
});
|
||||
|
||||
const client = testClient(app);
|
||||
|
||||
const res = await client.test.$get();
|
||||
const data = await res.json();
|
||||
|
||||
expect(data.requestId).toBeTruthy();
|
||||
expect(typeof data.requestId).toBe('string');
|
||||
expect(data.timestamp).toBeTruthy();
|
||||
expect(typeof data.timestamp).toBe('number');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Testing Error Handling
|
||||
|
||||
```typescript
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { ZodError } from 'zod';
|
||||
|
||||
describe('Error Handling', () => {
|
||||
test('HTTPException returns correct status and message', async () => {
|
||||
const app = new Hono()
|
||||
.get('/error', () => {
|
||||
throw new HTTPException(418, { message: "I'm a teapot" });
|
||||
})
|
||||
.onError((err, c) => {
|
||||
if (err instanceof HTTPException) {
|
||||
return c.json({ error: err.message }, err.status);
|
||||
}
|
||||
return c.json({ error: 'Internal error' }, 500);
|
||||
});
|
||||
|
||||
const client = testClient(app);
|
||||
|
||||
const res = await client.error.$get();
|
||||
|
||||
expect(res.status).toBe(418);
|
||||
|
||||
const error = await res.json();
|
||||
expect(error.error).toBe("I'm a teapot");
|
||||
});
|
||||
|
||||
test('Validation errors handled correctly', async () => {
|
||||
const Schema = z.object({
|
||||
email: z.string().email(),
|
||||
age: z.number().int().positive(),
|
||||
});
|
||||
|
||||
const app = new Hono()
|
||||
.post('/validate', zValidator('json', Schema), (c) => {
|
||||
const data = c.req.valid('json');
|
||||
return c.json({ data });
|
||||
})
|
||||
.onError((err, c) => {
|
||||
if (err instanceof ZodError) {
|
||||
return c.json({
|
||||
error: 'Validation failed',
|
||||
issues: err.issues,
|
||||
}, 400);
|
||||
}
|
||||
return c.json({ error: 'Internal error' }, 500);
|
||||
});
|
||||
|
||||
const client = testClient(app);
|
||||
|
||||
const res = await client.validate.$post({
|
||||
json: { email: 'invalid', age: -1 } as any
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
|
||||
const error = await res.json();
|
||||
expect(error.error).toBe('Validation failed');
|
||||
expect(error.issues).toBeTruthy();
|
||||
expect(Array.isArray(error.issues)).toBe(true);
|
||||
});
|
||||
|
||||
test('Generic errors sanitized in production', async () => {
|
||||
const originalEnv = process.env.NODE_ENV;
|
||||
|
||||
const app = new Hono()
|
||||
.get('/error', () => {
|
||||
throw new Error('Sensitive internal error');
|
||||
})
|
||||
.onError((err, c) => {
|
||||
const isDev = process.env.NODE_ENV !== 'production';
|
||||
return c.json({
|
||||
error: isDev ? err.message : 'Internal server error'
|
||||
}, 500);
|
||||
});
|
||||
|
||||
const client = testClient(app);
|
||||
|
||||
// Development mode
|
||||
process.env.NODE_ENV = 'development';
|
||||
let res = await client.error.$get();
|
||||
let error = await res.json();
|
||||
expect(error.error).toBe('Sensitive internal error');
|
||||
|
||||
// Production mode
|
||||
process.env.NODE_ENV = 'production';
|
||||
res = await client.error.$get();
|
||||
error = await res.json();
|
||||
expect(error.error).toBe('Internal server error');
|
||||
|
||||
// Restore
|
||||
process.env.NODE_ENV = originalEnv;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Integration Testing with Database
|
||||
|
||||
```typescript
|
||||
describe('Integration Tests', () => {
|
||||
let db: Database;
|
||||
|
||||
beforeEach(() => {
|
||||
db = new Database(':memory:');
|
||||
|
||||
// Create schema
|
||||
db.run(`
|
||||
CREATE TABLE users (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
db.run(`
|
||||
CREATE TABLE posts (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
)
|
||||
`);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
db.close();
|
||||
});
|
||||
|
||||
test('Full user and post workflow', async () => {
|
||||
const app = new Hono()
|
||||
.post('/users', zValidator('json', z.object({
|
||||
email: z.string().email(),
|
||||
name: z.string(),
|
||||
})), (c) => {
|
||||
const data = c.req.valid('json');
|
||||
const user = db.query(
|
||||
'INSERT INTO users (id, email, name) VALUES (?, ?, ?) RETURNING *'
|
||||
).get(crypto.randomUUID(), data.email, data.name);
|
||||
return c.json({ user }, 201);
|
||||
})
|
||||
.get('/users/:id/posts', (c) => {
|
||||
const posts = db.query(
|
||||
'SELECT * FROM posts WHERE user_id = ?'
|
||||
).all(c.req.param('id'));
|
||||
return c.json({ posts });
|
||||
})
|
||||
.post('/posts', zValidator('json', z.object({
|
||||
userId: z.string(),
|
||||
title: z.string(),
|
||||
content: z.string(),
|
||||
})), (c) => {
|
||||
const data = c.req.valid('json');
|
||||
const post = db.query(
|
||||
'INSERT INTO posts (id, user_id, title, content) VALUES (?, ?, ?, ?) RETURNING *'
|
||||
).get(crypto.randomUUID(), data.userId, data.title, data.content);
|
||||
return c.json({ post }, 201);
|
||||
});
|
||||
|
||||
const client = testClient(app);
|
||||
|
||||
// Create user
|
||||
const userRes = await client.users.$post({
|
||||
json: { email: 'alice@example.com', name: 'Alice' }
|
||||
});
|
||||
expect(userRes.status).toBe(201);
|
||||
const { user } = await userRes.json();
|
||||
|
||||
// Create posts for user
|
||||
await client.posts.$post({
|
||||
json: { userId: user.id, title: 'Post 1', content: 'Content 1' }
|
||||
});
|
||||
await client.posts.$post({
|
||||
json: { userId: user.id, title: 'Post 2', content: 'Content 2' }
|
||||
});
|
||||
|
||||
// Get user's posts
|
||||
const postsRes = await client.users[':id'].posts.$get({
|
||||
param: { id: user.id }
|
||||
});
|
||||
|
||||
expect(postsRes.status).toBe(200);
|
||||
|
||||
const { posts } = await postsRes.json();
|
||||
expect(posts).toHaveLength(2);
|
||||
expect(posts.map((p: any) => p.title)).toEqual(['Post 1', 'Post 2']);
|
||||
});
|
||||
|
||||
test('Transaction rollback on error', async () => {
|
||||
const app = new Hono()
|
||||
.post('/bulk-create', zValidator('json', z.object({
|
||||
users: z.array(z.object({ email: z.string(), name: z.string() }))
|
||||
})), (c) => {
|
||||
const data = c.req.valid('json');
|
||||
|
||||
try {
|
||||
db.transaction(() => {
|
||||
for (const user of data.users) {
|
||||
db.run(
|
||||
'INSERT INTO users (id, email, name) VALUES (?, ?, ?)',
|
||||
[crypto.randomUUID(), user.email, user.name]
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
||||
return c.json({ created: data.users.length }, 201);
|
||||
} catch (err) {
|
||||
throw new HTTPException(400, { message: 'Bulk create failed' });
|
||||
}
|
||||
});
|
||||
|
||||
const client = testClient(app);
|
||||
|
||||
// Attempt to create users with duplicate email
|
||||
const res = await client['bulk-create'].$post({
|
||||
json: {
|
||||
users: [
|
||||
{ email: 'alice@example.com', name: 'Alice' },
|
||||
{ email: 'alice@example.com', name: 'Alice Duplicate' }, // Duplicate!
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
|
||||
// Verify no users were created
|
||||
const count = db.query('SELECT COUNT(*) as count FROM users').get() as { count: number };
|
||||
expect(count.count).toBe(0);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Mocking Patterns
|
||||
|
||||
```typescript
|
||||
describe('Mocking', () => {
|
||||
test('Mock external API calls', async () => {
|
||||
// Mock fetch
|
||||
const originalFetch = global.fetch;
|
||||
global.fetch = async (url: string | URL | Request) => {
|
||||
return new Response(JSON.stringify({ mocked: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
};
|
||||
|
||||
const app = new Hono()
|
||||
.get('/proxy', async (c) => {
|
||||
const res = await fetch('https://api.example.com/data');
|
||||
const data = await res.json();
|
||||
return c.json({ data });
|
||||
});
|
||||
|
||||
const client = testClient(app);
|
||||
|
||||
const res = await client.proxy.$get();
|
||||
const data = await res.json();
|
||||
|
||||
expect(data.data.mocked).toBe(true);
|
||||
|
||||
// Restore
|
||||
global.fetch = originalFetch;
|
||||
});
|
||||
|
||||
test('Mock database with interface', async () => {
|
||||
interface IDatabase {
|
||||
query(sql: string): { get(id: string): any };
|
||||
}
|
||||
|
||||
class MockDatabase implements IDatabase {
|
||||
private data = new Map([
|
||||
['1', { id: '1', name: 'Alice' }],
|
||||
['2', { id: '2', name: 'Bob' }],
|
||||
]);
|
||||
|
||||
query(sql: string) {
|
||||
return {
|
||||
get: (id: string) => this.data.get(id),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const mockDb = new MockDatabase();
|
||||
|
||||
const app = new Hono()
|
||||
.get('/users/:id', (c) => {
|
||||
const user = mockDb.query('SELECT * FROM users WHERE id = ?').get(c.req.param('id'));
|
||||
if (!user) {
|
||||
throw new HTTPException(404, { message: 'User not found' });
|
||||
}
|
||||
return c.json({ user });
|
||||
});
|
||||
|
||||
const client = testClient(app);
|
||||
|
||||
const res = await client.users[':id'].$get({ param: { id: '1' } });
|
||||
const data = await res.json();
|
||||
|
||||
expect(data.user.name).toBe('Alice');
|
||||
});
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user