mirror of
https://github.com/infinite-iroha/touka.git
synced 2026-02-03 00:41:10 +08:00
140 lines
5.9 KiB
Go
140 lines
5.9 KiB
Go
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
|
||
}
|