touka/testutil.go
2025-07-24 08:07:38 +00:00

144 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Copyright 2024 WJQSERVER. All rights reserved.
// All rights reserved by WJQSERVER, related rights can be exercised by the infinite-iroha organization.
package touka
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
)
// CreateTestContext 为测试创建一个 *Context 和一个关联的 *Engine。
// 它使用 httptest.NewRecorder() (如果传入的 w 为 nil) 来捕获响应。
// 返回的 Context 已经过初始化,其 Writer 指向提供的 ResponseWriter (或新创建的 Recorder)
// 其 Request 是一个默认的 "GET /" 请求,并且其 engine 字段指向返回的 Engine 实例。
//
// 参数:
// - w (http.ResponseWriter): 可选的。如果为 nil函数内部会创建一个 httptest.ResponseRecorder。
// 通常在测试中,你会传入一个 httptest.ResponseRecorder 来检查响应。
//
// 返回:
// - c (*Context): 一个初始化的 Touka Context。
// - r (*Engine): 一个新的 Touka Engine 实例,与 c 相关联。
func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
// 1. 如果未提供 ResponseWriter则创建一个测试用的 Recorder
// ResponseRecorder 实现了 http.ResponseWriter 接口
var testResponseWriter http.ResponseWriter = w
if testResponseWriter == nil {
testResponseWriter = httptest.NewRecorder()
}
// 2. 创建一个新的 Engine 实例
// 使用 New() 而不是 Default() 以获得一个“干净”的引擎,不带默认中间件 (如 Recovery)
// 如果你的测试依赖于 Default() 的中间件,可以改为 r = Default()
r = New()
// 3. 从 Engine 的池中获取一个 Context 对象
// 这是模拟真实请求处理的最佳方式
c = r.pool.Get().(*Context)
// 4. 创建一个默认的 HTTP 请求
// 测试时可以根据需要修改这个请求的 Method, URL, Body, Headers 等
// http.NewRequest 的 body 可以是 nil (对于GET) 或 bytes.NewBufferString("body content") 等
req, err := http.NewRequest(http.MethodGet, "/", nil)
if err != nil {
// NewRequest 对于 "GET" 和 "/" 以及 nil body 通常不会失败
// 但作为健壮性考虑,可以 panic 或返回错误
panic("touka.CreateTestContext: Failed to create dummy request: " + err.Error()) // 英文 panic
}
// 确保请求有关联的 Context (尽管 c.reset 也会设置)
// req = req.WithContext(context.Background()) // 通常 reset 会处理这个
// 5. 重置/初始化 Context
// c.reset() 方法期望一个 http.ResponseWriter 和 *http.Request
// 它会将 c.Writer 包装成 touka.ResponseWriter (responseWriterImpl)
// 并设置 c.Request, c.Params (清空), c.handlers (nil), c.index (-1),
// c.Keys (新map), c.Errors (清空), c.ctx (来自 req.Context()),
// 以及 c.engine (在 c.pool.New 中已经设置,但 reset 会确保其他关联正确)。
c.reset(testResponseWriter, req)
// 确保 Context 中的 engine 字段指向我们创建的这个 Engine 实例
// 虽然 c.pool.New 应该已经做了,但显式确认或设置无害,尤其是如果我们不完全依赖 pool.New 的细节。
// 在当前的 Context.reset 实现中c.engine 是在从池中 New() 时由 Engine 自身设置的,
// reset 方法不会改变它。所以只要 c 是从 r.pool 获取的c.engine 就应该是 r。
return c, r
}
// CreateTestContextWithRequest 功能与 CreateTestContext 类似,但允许传入自定义的 *http.Request。
// 这对于测试需要特定请求方法、URL、头部或Body的处理器非常有用。
//
// 参数:
// - w (http.ResponseWriter): 可选。如果为 nil创建一个 httptest.ResponseRecorder。
// - req (*http.Request): 用户提供的 HTTP 请求。如果为 nil则内部创建一个默认的 "GET /"。
//
// 返回:
// - c (*Context): 一个初始化的 Touka Context。
// - r (*Engine): 一个新的 Touka Engine 实例,与 c 相关联。
func CreateTestContextWithRequest(w http.ResponseWriter, req *http.Request) (c *Context, r *Engine) {
var testResponseWriter http.ResponseWriter = w
if testResponseWriter == nil {
testResponseWriter = httptest.NewRecorder()
}
r = New() // 创建 Engine
c = r.pool.Get().(*Context) // 从池获取 Context
var finalReq *http.Request = req
if finalReq == nil { // 如果未提供请求,创建默认请求
var err error
finalReq, err = http.NewRequest(http.MethodGet, "/", nil)
if err != nil {
panic("touka.CreateTestContextWithRequest: Failed to create dummy request: " + err.Error()) // 英文 panic
}
}
c.reset(testResponseWriter, finalReq) // 使用提供的或默认的请求重置 Context
// c.engine 已由 r.pool.New 设置为 r
return c, r
}
// PerformRequest 在给定的 Engine 上执行一个模拟的 HTTP 请求,并返回响应记录器。
// 这是一个更高级别的测试辅助函数封装了创建请求、Context 和执行引擎的 ServeHTTP 方法。
//
// 参数:
// - engine (*Engine): 要测试的 Touka 引擎实例。
// - method (string): HTTP 请求方法 (例如 "GET", "POST")。
// - path (string): 请求的路径 (例如 "/", "/users/123?name=test")。
// - body (io.Reader): 可选的请求体。对于 GET, HEAD 等通常为 nil。
// - headers (http.Header): 可选的请求头部。
//
// 返回:
// - *httptest.ResponseRecorder: 包含响应状态、头部和主体的记录器。
//
// 示例:
//
// rr := touka.PerformRequest(myEngine, "GET", "/ping", nil, nil)
// assert.Equal(t, http.StatusOK, rr.Code)
// assert.Equal(t, "pong", rr.Body.String())
func PerformRequest(engine *Engine, method, path string, body io.Reader, headers http.Header) *httptest.ResponseRecorder {
req, err := http.NewRequest(method, path, body)
if err != nil {
// 通常 NewRequest 对于合法的方法和路径不会失败(除非路径解析错误)
panic(fmt.Sprintf("touka.PerformRequest: Failed to create request %s %s: %v", method, path, err)) // 英文 panic
}
// 设置请求头部 (如果提供)
if headers != nil {
req.Header = headers
}
// 创建一个 ResponseRecorder 来捕获响应
rr := httptest.NewRecorder()
// 直接调用 Engine 的 ServeHTTP 方法来处理请求
// Engine 会负责创建和管理 Context
engine.ServeHTTP(rr, req)
return rr
}