webtest

package
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: MIT Imports: 6 Imported by: 0

README

webtest 包 — Web 测试

所属层级: Infrastructure Layer
设计理念: 链式 API,简化测试
设计灵感: Spring WebTestClient

概述

webtest 包提供 Web 测试客户端功能,参考 Spring WebTestClient 设计。支持链式构建 HTTP 请求和断言响应,简化 Web 层集成测试的编写。

核心功能
功能 说明
链式 API 流畅的链式调用,提高测试代码可读性
请求构建 支持 GET/POST/PUT/DELETE/PATCH 请求
响应断言 丰富的状态码、响应头、响应体断言方法
JSON 支持 自动序列化/反序列化 JSON 数据
调试支持 提供响应打印功能,方便调试

核心接口

WebTestClient Web 测试客户端
type WebTestClient struct {
    handler http.Handler
    baseURL string
    headers map[string]string
}
创建
client := webtest.NewWebTestClient(handler)
便捷函数
client := webtest.CreateWebTestClient(handler)
配置方法
方法 说明 返回值
BaseURL(url string) 设置基础 URL *WebTestClient
Header(name, value string) 设置默认请求头 *WebTestClient
HTTP 方法
方法 说明 返回值
Get(path string) 发起 GET 请求 *RequestSpec
Post(path string) 发起 POST 请求 *RequestSpec
Put(path string) 发起 PUT 请求 *RequestSpec
Delete(path string) 发起 DELETE 请求 *RequestSpec
Patch(path string) 发起 PATCH 请求 *RequestSpec
RequestSpec 请求规范
type RequestSpec struct {
    // ...
}
请求配置
方法 说明 返回值
Header(name, value string) 设置请求头 *RequestSpec
ContentType(contentType string) 设置 Content-Type *RequestSpec
Body(body io.Reader) 设置请求体 *RequestSpec
JSON(data any) 设置 JSON 请求体 *RequestSpec
Exchange() 发起请求并返回响应规范 *ResponseSpec
ResponseSpec 响应规范
type ResponseSpec struct {
    recorder *httptest.ResponseRecorder
}
状态码断言
方法 说明
Status(expected int) 断言状态码
StatusIsOk() 断言 200
StatusIsCreated() 断言 201
StatusIsNoContent() 断言 204
StatusIsBadRequest() 断言 400
StatusIsUnauthorized() 断言 401
StatusIsForbidden() 断言 403
StatusIsNotFound() 断言 404
响应头断言
方法 说明
Header(name, expected string) 断言响应头
响应体断言
方法 说明
Body() 获取响应体字符串
JSONBody(target any) 解析 JSON 响应体
BodyContains(substring string) 断言响应体包含子串
BodyEquals(expected string) 断言响应体等于字符串
调试方法
方法 说明
Print() 打印响应信息
Recorder() 获取底层 ResponseRecorder

快速开始

基本使用
package main

import (
    "net/http"
    "testing"
    "github.com/xudefa/enhance/webtest"
)

func TestHelloEndpoint(t *testing.T) {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("Hello, World!"))
    })

    client := webtest.NewWebTestClient(handler)
    client.Get("/api/hello").
        Exchange().
        StatusIsOk().
        BodyContains("Hello")
}

API 参考

POST JSON 请求
func TestCreateUser(t *testing.T) {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        var user User
        json.NewDecoder(r.Body).Decode(&user)
        
        user.ID = "123"
        w.WriteHeader(http.StatusCreated)
        json.NewEncoder(w).Encode(user)
    })

    client := webtest.NewWebTestClient(handler)

    var result User
    client.Post("/api/users").
        JSON(map[string]string{
            "name": "John",
            "email": "john@example.com",
        }).
        Exchange().
        StatusIsCreated().
        JSONBody(&result)

    if result.ID != "123" {
        t.Errorf("expected ID 123, got %s", result.ID)
    }
}
带请求头的请求
func TestAuthenticatedRequest(t *testing.T) {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token != "Bearer valid-token" {
            w.WriteHeader(http.StatusUnauthorized)
            return
        }
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("Authenticated"))
    })

    client := webtest.NewWebTestClient(handler)
    client.Get("/api/protected").
        Header("Authorization", "Bearer valid-token").
        Exchange().
        StatusIsOk().
        BodyContains("Authenticated")
}
测试 404 响应
func TestNotFound(t *testing.T) {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusNotFound)
        w.Write([]byte("Not Found"))
    })

    client := webtest.NewWebTestClient(handler)
    client.Get("/api/nonexistent").
        Exchange().
        StatusIsNotFound()
}
调试响应
func TestDebugResponse(t *testing.T) {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"message": "Hello"}`))
    })

    client := webtest.NewWebTestClient(handler)
    client.Get("/api/hello").
        Exchange().
        Print() // 打印响应信息用于调试
}

输出:

Status: 200
Headers: map[Content-Type:[application/json]]
Body: {"message": "Hello"}

使用示例

场景 1: REST API 集成测试

测试 REST API 的 CRUD 操作:

func TestUserCRUD(t *testing.T) {
    handler := setupTestHandler()
    client := webtest.NewWebTestClient(handler)

    // Create
    var created User
    client.Post("/api/users").
        JSON(CreateUserRequest{Name: "John", Email: "john@example.com"}).
        Exchange().
        StatusIsCreated().
        JSONBody(&created)

    // Read
    var retrieved User
    client.Get("/api/users/" + created.ID).
        Exchange().
        StatusIsOk().
        JSONBody(&retrieved)

    if retrieved.Name != "John" {
        t.Errorf("expected name John, got %s", retrieved.Name)
    }

    // Update
    client.Put("/api/users/" + created.ID).
        JSON(UpdateUserRequest{Name: "Jane"}).
        Exchange().
        StatusIsOk()

    // Delete
    client.Delete("/api/users/" + created.ID).
        Exchange().
        StatusIsNoContent()

    // Verify deleted
    client.Get("/api/users/" + created.ID).
        Exchange().
        StatusIsNotFound()
}

最佳实践:

  • 测试完整的 CRUD 流程
  • 验证每个操作的响应状态
  • 验证数据一致性
场景 2: 认证和授权测试

测试 API 的认证和授权逻辑:

func TestAuthentication(t *testing.T) {
    handler := setupTestHandler()
    client := webtest.NewWebTestClient(handler)

    // 无认证头访问受保护资源
    client.Get("/api/protected").
        Exchange().
        StatusIsUnauthorized()

    // 使用有效 token 访问
    client.Get("/api/protected").
        Header("Authorization", "Bearer valid-token").
        Exchange().
        StatusIsOk()

    // 使用无效 token 访问
    client.Get("/api/protected").
        Header("Authorization", "Bearer invalid-token").
        Exchange().
        StatusIsUnauthorized()
}

func TestAuthorization(t *testing.T) {
    handler := setupTestHandler()
    client := webtest.NewWebTestClient(handler)

    // 普通用户访问管理员接口
    client.Get("/api/admin/users").
        Header("Authorization", "Bearer user-token").
        Exchange().
        StatusIsForbidden()

    // 管理员访问管理员接口
    client.Get("/api/admin/users").
        Header("Authorization", "Bearer admin-token").
        Exchange().
        StatusIsOk()
}

最佳实践:

  • 测试未认证、认证失败、认证成功场景
  • 测试权限不足和权限足够场景
  • 使用不同的 token 测试不同角色
场景 3: 错误处理测试

测试 API 的错误处理逻辑:

func TestValidationErrors(t *testing.T) {
    handler := setupTestHandler()
    client := webtest.NewWebTestClient(handler)

    // 缺少必填字段
    client.Post("/api/users").
        JSON(map[string]string{"email": "test@example.com"}).
        Exchange().
        StatusIsBadRequest().
        BodyContains("name is required")

    // 邮箱格式错误
    client.Post("/api/users").
        JSON(map[string]string{
            "name": "John",
            "email": "invalid-email",
        }).
        Exchange().
        StatusIsBadRequest().
        BodyContains("invalid email format")
}

func TestNotFoundError(t *testing.T) {
    handler := setupTestHandler()
    client := webtest.NewWebTestClient(handler)

    client.Get("/api/users/nonexistent").
        Exchange().
        StatusIsNotFound().
        BodyContains("user not found")
}

最佳实践:

  • 测试各种验证错误场景
  • 验证错误响应格式和消息
  • 测试资源不存在场景
场景 4: 分页和过滤测试

测试 API 的分页和过滤功能:

func TestPagination(t *testing.T) {
    handler := setupTestHandler()
    client := webtest.NewWebTestClient(handler)

    // 第一页
    var page1 UserListResponse
    client.Get("/api/users?page=1&size=10").
        Exchange().
        StatusIsOk().
        JSONBody(&page1)

    if len(page1.Users) > 10 {
        t.Errorf("expected max 10 users, got %d", len(page1.Users))
    }

    // 第二页
    var page2 UserListResponse
    client.Get("/api/users?page=2&size=10").
        Exchange().
        StatusIsOk().
        JSONBody(&page2)

    // 验证分页信息
    if page2.Page != 2 {
        t.Errorf("expected page 2, got %d", page2.Page)
    }
}

func TestFiltering(t *testing.T) {
    handler := setupTestHandler()
    client := webtest.NewWebTestClient(handler)

    // 按状态过滤
    var activeUsers UserListResponse
    client.Get("/api/users?status=active").
        Exchange().
        StatusIsOk().
        JSONBody(&activeUsers)

    for _, user := range activeUsers.Users {
        if user.Status != "active" {
            t.Errorf("expected active user, got %s", user.Status)
        }
    }
}

最佳实践:

  • 测试分页参数验证
  • 测试过滤条件组合
  • 验证分页响应格式

最佳实践

1. 使用链式调用提高可读性
// ✅ 推荐:使用链式调用
client.Post("/api/users").
    JSON(CreateUserRequest{Name: "John"}).
    Exchange().
    StatusIsCreated().
    JSONBody(&result)

// ⚠️ 不推荐:分步调用
req := client.Post("/api/users")
req = req.JSON(CreateUserRequest{Name: "John"})
resp := req.Exchange()
resp.StatusIsCreated()
resp.JSONBody(&result)
2. 测试完整的错误场景
// ✅ 推荐:测试各种错误场景
client.Post("/api/users").
    JSON(map[string]string{}).
    Exchange().
    StatusIsBadRequest()

client.Post("/api/users").
    JSON(map[string]string{"email": "invalid"}).
    Exchange().
    StatusIsBadRequest()

client.Get("/api/users/nonexistent").
    Exchange().
    StatusIsNotFound()

// ⚠️ 不推荐:只测试成功场景
client.Post("/api/users").
    JSON(CreateUserRequest{Name: "John"}).
    Exchange().
    StatusIsCreated()
3. 使用 JSONBody 自动反序列化
// ✅ 推荐:使用 JSONBody 自动反序列化
var result User
client.Get("/api/users/123").
    Exchange().
    StatusIsOk().
    JSONBody(&result)

// ⚠️ 不推荐:手动解析 JSON
resp := client.Get("/api/users/123").Exchange()
body := resp.Body()
json.Unmarshal([]byte(body), &result)
4. 使用 Print 调试
// ✅ 推荐:调试时使用 Print
client.Get("/api/users/123").
    Exchange().
    Print(). // 打印响应信息
    StatusIsOk()

// ⚠️ 不推荐:手动打印响应
resp := client.Get("/api/users/123").Exchange()
fmt.Printf("Status: %d\n", resp.Recorder().Code)
fmt.Printf("Body: %s\n", resp.Recorder().Body.String())
5. 与测试框架集成
// ✅ 推荐:在测试套件中复用客户端
func TestSuite(t *testing.T) {
    handler := setupTestHandler()
    client := webtest.NewWebTestClient(handler)

    t.Run("CreateUser", func(t *testing.T) {
        testCreateUser(t, client)
    })

    t.Run("GetUser", func(t *testing.T) {
        testGetUser(t, client)
    })

    t.Run("UpdateUser", func(t *testing.T) {
        testUpdateUser(t, client)
    })
}

// ⚠️ 不推荐:每个测试都创建新的 handler
func TestCreateUser(t *testing.T) {
    handler := setupTestHandler() // 重复创建
    client := webtest.NewWebTestClient(handler)
    // ...
}
6. 设计要点
  • 使用 httptest.ResponseRecorder 记录响应
  • 链式 API 提高测试代码可读性
  • 丰富的断言方法简化测试编写
  • JSON 自动序列化/反序列化
  • 零外部依赖,仅使用 Go 标准库

Documentation

Overview

Package webtest 提供 Web 测试工具,用于 enhance 框架。

该模块提供 HTTP 客户端测试工具,用于测试 Web 端点和控制器。 支持模拟 HTTP 请求、验证响应、测试中间件等功能。

架构设计

  • TestServer: 测试服务器接口,用于启动临时 HTTP 服务
  • TestClient: 测试客户端接口,用于发送 HTTP 请求
  • RequestBuilder: 请求构建器接口,支持链式配置
  • ResponseVerifier: 响应验证器接口,提供便捷的断言方法

核心功能

  • 模拟请求: 支持 GET、POST、PUT、DELETE 等 HTTP 方法
  • 响应验证: 支持状态码、响应头、响应体验证
  • JSON 验证: 支持 JSON 响应体验证
  • 中间件测试: 支持测试 HTTP 中间件

使用方式

创建测试服务器:

server := webtest.NewTestServer(router)
defer server.Close()

发送请求:

resp := server.GET("/api/users").
    WithHeader("Authorization", "Bearer token").
    Execute()

验证响应:

resp.AssertStatus(200)
resp.AssertJSONPath("$.users[0].name", "John")
resp.AssertBodyContains("success")

请求构建

支持链式构建请求:

server.POST("/api/users").
    WithJSON(map[string]any{"name": "John"}).
    WithHeader("Content-Type", "application/json").
    WithQuery("page", "1").
    Execute()

响应断言

提供丰富的断言方法:

  • AssertStatus: 断言 HTTP 状态码
  • AssertHeader: 断言响应头
  • AssertBody: 断言响应体
  • AssertJSONPath: 断言 JSON 路径值
  • AssertBodyContains: 断言响应体包含指定字符串

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type RequestBuilder

type RequestBuilder interface {
	// WithHeader 设置请求头。
	WithHeader(name, value string) RequestBuilder

	// WithJSON 设置 JSON 请求体。
	WithJSON(data any) RequestBuilder

	// WithBody 设置请求体。
	WithBody(body []byte) RequestBuilder

	// WithQuery 设置查询参数。
	WithQuery(key, value string) RequestBuilder

	// Execute 发起请求并返回响应验证器。
	Execute() ResponseVerifier
}

RequestBuilder 请求构建器接口。

支持链式构建 HTTP 请求。

type RequestSpec

type RequestSpec struct {
	// contains filtered or unexported fields
}

RequestSpec 请求规范,用于链式构建 HTTP 请求。

func (*RequestSpec) Body

func (r *RequestSpec) Body(body io.Reader) *RequestSpec

Body 设置请求体。

func (*RequestSpec) ContentType

func (r *RequestSpec) ContentType(contentType string) *RequestSpec

ContentType 设置 Content-Type。

func (*RequestSpec) Exchange

func (r *RequestSpec) Exchange() *ResponseSpec

Exchange 发起请求并返回响应规范。

func (*RequestSpec) Header

func (r *RequestSpec) Header(name, value string) *RequestSpec

Header 设置请求头。

func (*RequestSpec) JSON

func (r *RequestSpec) JSON(data any) *RequestSpec

JSON 设置 JSON 请求体。

type ResponseSpec

type ResponseSpec struct {
	// contains filtered or unexported fields
}

ResponseSpec 响应规范,用于链式断言 HTTP 响应。

func (*ResponseSpec) Body

func (r *ResponseSpec) Body() string

Body 获取响应体字符串。

func (*ResponseSpec) BodyContains

func (r *ResponseSpec) BodyContains(substring string) *ResponseSpec

BodyContains 断言响应体包含指定子串。

func (*ResponseSpec) BodyEquals

func (r *ResponseSpec) BodyEquals(expected string) *ResponseSpec

BodyEquals 断言响应体等于指定字符串。

func (*ResponseSpec) Header

func (r *ResponseSpec) Header(name, expected string) *ResponseSpec

Header 断言响应头。

func (*ResponseSpec) JSONBody

func (r *ResponseSpec) JSONBody(target any) *ResponseSpec

JSONBody 解析 JSON 响应体到目标对象。

func (*ResponseSpec) Print

func (r *ResponseSpec) Print() *ResponseSpec

Print 打印响应信息(调试用)。

func (*ResponseSpec) Recorder

func (r *ResponseSpec) Recorder() *httptest.ResponseRecorder

Recorder 获取底层 ResponseRecorder。

func (*ResponseSpec) Status

func (r *ResponseSpec) Status(expected int) *ResponseSpec

Status 断言状态码。

func (*ResponseSpec) StatusIsBadRequest

func (r *ResponseSpec) StatusIsBadRequest() *ResponseSpec

StatusIsBadRequest 断言 400 状态码。

func (*ResponseSpec) StatusIsCreated

func (r *ResponseSpec) StatusIsCreated() *ResponseSpec

StatusIsCreated 断言 201 状态码。

func (*ResponseSpec) StatusIsForbidden

func (r *ResponseSpec) StatusIsForbidden() *ResponseSpec

StatusIsForbidden 断言 403 状态码。

func (*ResponseSpec) StatusIsNoContent

func (r *ResponseSpec) StatusIsNoContent() *ResponseSpec

StatusIsNoContent 断言 204 状态码。

func (*ResponseSpec) StatusIsNotFound

func (r *ResponseSpec) StatusIsNotFound() *ResponseSpec

StatusIsNotFound 断言 404 状态码。

func (*ResponseSpec) StatusIsOk

func (r *ResponseSpec) StatusIsOk() *ResponseSpec

StatusIsOk 断言 200 状态码。

func (*ResponseSpec) StatusIsUnauthorized

func (r *ResponseSpec) StatusIsUnauthorized() *ResponseSpec

StatusIsUnauthorized 断言 401 状态码。

type ResponseVerifier

type ResponseVerifier interface {
	// AssertStatus 断言 HTTP 状态码。
	AssertStatus(expected int) ResponseVerifier

	// AssertHeader 断言响应头。
	AssertHeader(name, expected string) ResponseVerifier

	// AssertBody 断言响应体。
	AssertBody(expected string) ResponseVerifier

	// AssertBodyContains 断言响应体包含指定字符串。
	AssertBodyContains(substring string) ResponseVerifier

	// Body 获取响应体字符串。
	Body() string

	// StatusCode 获取 HTTP 状态码。
	StatusCode() int
}

ResponseVerifier 响应验证器接口。

提供便捷的断言方法验证 HTTP 响应。

type TestServer

type TestServer interface {
	// GET 发起 GET 请求并返回请求构建器。
	GET(path string) RequestBuilder

	// POST 发起 POST 请求并返回请求构建器。
	POST(path string) RequestBuilder

	// PUT 发起 PUT 请求并返回请求构建器。
	PUT(path string) RequestBuilder

	// DELETE 发起 DELETE 请求并返回请求构建器。
	DELETE(path string) RequestBuilder

	// Patch 发起 PATCH 请求并返回请求构建器。
	Patch(path string) RequestBuilder

	// Close 关闭测试服务器。
	Close() error

	// URL 获取服务器 URL。
	URL() string
}

TestServer 测试服务器接口。

用于启动临时 HTTP 服务,测试 Web 端点和控制器。

type WebTestClient

type WebTestClient struct {
	// contains filtered or unexported fields
}

WebTestClient Web 测试客户端。

func CreateWebTestClient

func CreateWebTestClient(handler http.Handler) *WebTestClient

CreateWebTestClient 创建 Web 测试客户端(便捷函数)。

func NewWebTestClient

func NewWebTestClient(handler http.Handler) *WebTestClient

NewWebTestClient 创建 Web 测试客户端。

func (*WebTestClient) BaseURL

func (c *WebTestClient) BaseURL(url string) *WebTestClient

BaseURL 设置基础 URL。

func (*WebTestClient) Delete

func (c *WebTestClient) Delete(path string) *RequestSpec

Delete 发起 DELETE 请求。

func (*WebTestClient) Get

func (c *WebTestClient) Get(path string) *RequestSpec

Get 发起 GET 请求。

func (*WebTestClient) Header

func (c *WebTestClient) Header(name, value string) *WebTestClient

Header 设置默认请求头。

func (*WebTestClient) Patch

func (c *WebTestClient) Patch(path string) *RequestSpec

Patch 发起 PATCH 请求。

func (*WebTestClient) Post

func (c *WebTestClient) Post(path string) *RequestSpec

Post 发起 POST 请求。

func (*WebTestClient) Put

func (c *WebTestClient) Put(path string) *RequestSpec

Put 发起 PUT 请求。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL