如何动态更改方法接收器

How to Dynamically Change Method Receiver

我最近了解到您可以在 go 中执行此操作:

type Env struct{}

func (e *Env) httpHandler(w http.ResponseWriter, r *http.Request) {
   //...
}

func main() {
    // ...
    e := &Env{}
    router := mux.NewRouter()
    router.HandleFunc("/foo", e.httpHandler)
}

这对于依赖注入非常有用,因为当我进行单元测试时,我可以简单地使用模拟环境调用 httpHandler

但是,我的问题是...假设您有:

method := e.httpHandler

e.httpHandler 已经通过反射或其他方式存储到 method 之后,有什么方法可以动态更改接收器 e 的值吗?我可以更改传递给 method() 的参数,但似乎接收器值已锁定,更改接收器的唯一方法是执行 e2.httpHandler。这在我的情况下是不可能的,因为我正在从 mux 路由器中提取 e.httpHandler 并且我只想在调用 httpHandler.[=22= 之前用不同的接收器换出 e ]

为了了解更多上下文,我正在使用 router.Walk() 来执行 table 驱动测试,我在其中遍历每条路线,调用处理程序,检查返回的响应是否正确,等等。但是,某些路由需要的数据库模拟与其他路由略有不同,因此对路由器中的所有路由使用通用的模拟接收器并不理想。我想用 select 处理程序的自定义模拟环境动态交换处理程序接收器。

这是我的想法。你怎么看?

type Env struct{
    db string // should be database, just example
}


func (e *Env) httpHandler(w http.ResponseWriter, r * http.Request) {

}

// In case, create different router by different database.
func newRouter(db string) *mux.Router {
    e := &Env{
        db:db,
    }
    router := mux.NewRouter()
    router.HandleFunc("/foo", e.httpHandler)
    router.HandleFunc("/bar", e.httpHandler)
    return router
}

func TestByDatabaseA(t *testing.T)  {
    r := newRouter("foo")
    r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
        tpl, _ := route.GetPathTemplate()
        if !strings.HasPrefix(tpl,"/foo"){
            return nil
        }
        // run test
        return nil
    })
}

func TestByDatabaseB(t *testing.T)  {
    r := newRouter("bar")
    r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
        tpl, _ := route.GetPathTemplate()
        if !strings.HasPrefix(tpl,"/bar"){
            return nil
        }
        // run test
        return nil
    })
}

如果您在测试套件的整个生命周期中都需要一个路由器 - 并且由于您无法更改指针接收器或重新注册路由处理程序 - 您可以尝试包装 HandlerFunc 可以更改处理程序的地方稍后运行:

type wrapperHandler struct {
    Fn http.HandlerFunc
}

func (wh *wrapperHandler) HandlerFunc() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        wh.Fn.ServeHTTP(w, r) // can change `Fn` later
    }
}

使用:

// setup
router := mux.NewRouter()
wh := wrapperHandler{} // fill in wh.Fn later
router.HandleFunc("/foo", wh.HandlerFunc())

e := Env{ /*db1*/ }
wh.Fn = e.httpHandler
runTest(router)

e = Env{ /*db2*/ }
wh.Fn = e.httpHandler
runTest(router)