Skip to content

单元测试

作为 WinterCG 的合规实现,我们可以使用 Request/Response 类来测试 Vafast 服务器。

Vafast 提供了 Server.fetch 方法,该方法接受 Web 标准 Request 并返回 Response,模拟 HTTP 请求。

Bun 包含一个内置的 测试运行器,通过 bun:test 模块提供类似 Jest 的 API,便于创建单元测试。

在项目根目录下创建 test/index.test.ts,内容如下:

typescript
// test/index.test.ts
import { describe, expect, it } from 'bun:test'
import { Server, defineRoutes, createHandler } from 'vafast'

describe('Vafast', () => {
    it('returns a response', async () => {
        const routes = defineRoutes([
          {
            method: 'GET',
            path: '/',
            handler: createHandler(() => 'hi')
          }
        ])
        
        const server = new Server(routes)

        const response = await server
            .fetch(new Request('http://localhost/'))
            .then((res) => res.text())

        expect(response).toBe('hi')
    })
})

然后我们可以通过运行 bun test 来进行测试。

bash
bun test

对 Vafast 服务器的新请求必须是一个完全有效的 URL,不能是 URL 的一部分。

请求必须提供如下格式的 URL:

URL有效
http://localhost/user
/user

我们还可以使用其他测试库,如 Jest 或其他测试库来创建 Vafast 单元测试。

路由测试

我们可以测试不同的路由和 HTTP 方法:

typescript
// test/routes.test.ts
import { describe, expect, it } from 'bun:test'
import { Server, defineRoutes, createHandler } from 'vafast'

describe('Vafast Routes', () => {
    const routes = defineRoutes([
      {
        method: 'GET',
        path: '/users/:id',
        handler: createHandler(({ params }) => `User ${params.id}`)
      },
      {
        method: 'POST',
        path: '/users',
        handler: createHandler(({ body }) => ({ id: 1, ...body }))
      }
    ])
    
    const server = new Server(routes)

    it('handles GET request with params', async () => {
        const response = await server
            .fetch(new Request('http://localhost/users/123'))
            .then((res) => res.text())

        expect(response).toBe('User 123')
    })

    it('handles POST request with body', async () => {
        const response = await server
            .fetch(new Request('http://localhost/users', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ name: 'John', email: 'john@example.com' })
            }))
            .then((res) => res.json())

        expect(response).toEqual({
            id: 1,
            name: 'John',
            email: 'john@example.com'
        })
    })
})

中间件测试

测试中间件的功能:

typescript
// test/middleware.test.ts
import { describe, expect, it } from 'bun:test'
import { Server, defineRoutes, createHandler } from 'vafast'

describe('Vafast Middleware', () => {
    const loggingMiddleware = async (req: Request, next: () => Promise<Response>) => {
        console.log(`${req.method} ${req.url}`)
        const response = await next()
        console.log(`Response: ${response.status}`)
        return response
    }

    const routes = defineRoutes([
      {
        method: 'GET',
        path: '/test',
        handler: createHandler(() => 'Hello from middleware')
      }
    ])
    
    const server = new Server(routes)
    server.use(loggingMiddleware)

    it('executes middleware correctly', async () => {
        const response = await server
            .fetch(new Request('http://localhost/test'))
            .then((res) => res.text())

        expect(response).toBe('Hello from middleware')
    })
})

验证测试

测试 TypeBox 验证功能:

typescript
// test/validation.test.ts
import { describe, expect, it } from 'bun:test'
import { Server, defineRoutes, createHandler } from 'vafast'
import { Type } from '@sinclair/typebox'

describe('Vafast Validation', () => {
    const userSchema = Type.Object({
        name: Type.String({ minLength: 1 }),
        email: Type.String({ format: 'email' }),
        age: Type.Number({ minimum: 0 })
    })

    const routes = defineRoutes([
      {
        method: 'POST',
        path: '/users',
        handler: createHandler(({ body }) => ({ success: true, user: body })),
        body: userSchema
      }
    ])
    
    const server = new Server(routes)

    it('validates valid request body', async () => {
        const response = await server
            .fetch(new Request('http://localhost/users', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    name: 'John',
                    email: 'john@example.com',
                    age: 25
                })
            }))
            .then((res) => res.json())

        expect(response.success).toBe(true)
        expect(response.user.name).toBe('John')
    })

    it('rejects invalid request body', async () => {
        const response = await server
            .fetch(new Request('http://localhost/users', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    name: '', // 违反 minLength: 1
                    email: 'invalid-email', // 违反 format: 'email'
                    age: -5 // 违反 minimum: 0
                })
            }))

        expect(response.status).toBe(400)
    })
})

错误处理测试

测试错误处理逻辑:

typescript
// test/error-handling.test.ts
import { describe, expect, it } from 'bun:test'
import { Server, defineRoutes, createHandler } from 'vafast'

describe('Vafast Error Handling', () => {
    const routes = defineRoutes([
      {
        method: 'GET',
        path: '/error',
        handler: createHandler(() => {
            throw new Error('Something went wrong')
        })
      },
      {
        method: 'GET',
        path: '/not-found',
        handler: createHandler(() => 'This should not be reached')
      }
    ])
    
    const server = new Server(routes)

    it('handles internal server errors', async () => {
        const response = await server
            .fetch(new Request('http://localhost/error'))

        expect(response.status).toBe(500)
    })

    it('returns 404 for non-existent routes', async () => {
        const response = await server
            .fetch(new Request('http://localhost/non-existent'))

        expect(response.status).toBe(404)
    })

    it('returns 405 for method not allowed', async () => {
        const response = await server
            .fetch(new Request('http://localhost/not-found', {
                method: 'POST'
            }))

        expect(response.status).toBe(405)
    })
})

集成测试

测试完整的应用流程:

typescript
// test/integration.test.ts
import { describe, expect, it } from 'bun:test'
import { Server, defineRoutes, createHandler } from 'vafast'

describe('Vafast Integration', () => {
    let server: Server

    beforeAll(() => {
        const routes = defineRoutes([
          {
            method: 'GET',
            path: '/health',
            handler: createHandler(() => ({ status: 'ok' }))
          },
          {
            method: 'GET',
            path: '/users/:id',
            handler: createHandler(({ params }) => ({
                id: params.id,
                name: 'Test User',
                email: 'test@example.com'
            }))
          },
          {
            method: 'POST',
            path: '/users',
            handler: createHandler(({ body }) => ({
                id: Date.now(),
                ...body,
                createdAt: new Date().toISOString()
            }))
          }
        ])
        
        server = new Server(routes)
    })

    it('performs complete user CRUD flow', async () => {
        // 1. 检查健康状态
        const healthResponse = await server
            .fetch(new Request('http://localhost/health'))
            .then((res) => res.json())

        expect(healthResponse.status).toBe('ok')

        // 2. 创建用户
        const createResponse = await server
            .fetch(new Request('http://localhost/users', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    name: 'Integration Test User',
                    email: 'integration@example.com'
                })
            }))
            .then((res) => res.json())

        expect(createResponse.name).toBe('Integration Test User')
        expect(createResponse.id).toBeDefined()
        expect(createResponse.createdAt).toBeDefined()

        // 3. 获取用户
        const userId = createResponse.id
        const getUserResponse = await server
            .fetch(new Request(`http://localhost/users/${userId}`))
            .then((res) => res.json())

        expect(getUserResponse.id).toBe(userId)
        expect(getUserResponse.name).toBe('Integration Test User')
    })
})

测试配置

package.json 中配置测试脚本:

json
{
  "scripts": {
    "test": "bun test",
    "test:watch": "bun test --watch",
    "test:coverage": "bun test --coverage"
  }
}

测试最佳实践

  1. 隔离测试:每个测试应该独立运行,不依赖其他测试的状态
  2. 清理资源:在测试后清理任何创建的资源
  3. 模拟外部依赖:使用 mock 来隔离外部服务
  4. 测试边界情况:测试正常流程和异常情况
  5. 保持测试简单:每个测试只测试一个功能点

总结

Vafast 的测试系统提供了:

  • ✅ 基于 WinterCG 标准的 Request/Response 测试
  • ✅ 完整的路由测试支持
  • ✅ 中间件测试能力
  • ✅ 验证系统测试
  • ✅ 错误处理测试
  • ✅ 集成测试支持

下一步

如果您有任何问题,请查看我们的 社区页面GitHub 仓库