如何动态更改方法接收器

huangapple go评论77阅读模式
英文:

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之前用不同的接收者替换e

更多背景信息,我正在使用router.Walk()来实现基本的表驱动测试,我遍历每个路由,调用处理程序,检查返回的响应是否正确,等等。然而,某些路由需要与其他路由略有不同的数据库模拟,因此对于路由器中的所有路由使用一个通用的模拟接收者并不理想。我希望能够动态地用自定义的模拟环境替换处理程序的接收者。

英文:

I recently learned you can do this in 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)
}

This is great for dependency injection since when I unit test I can simply call httpHandler with a mock env.

However, my question is... say you have:

method := e.httpHandler

Is there any way to dynamically change the value of the receiver e after e.httpHandler has already been stored into method with reflection or something? I can change the parameters passed into method() but it seems like the receiver value is locked in and the only way to change the receiver would be to do e2.httpHandler. This isn't possible in my case because I'm extracting e.httpHandler from the mux router and I just want to swap out e with a different receiver before calling httpHandler.

For more context I'm using router.Walk() to essentially do table driven tests where I iterate through every route, call the handler, check that the returned response is the correct shape, etc. However, some routes need slightly different database mocks than others and so it's not ideal to use a one-size-fits-all mock receiver for all routes in the router. I wanted to dynamically swap out the handler receiver with custom mock environments for select handlers.

答案1

得分: 1

这是我的想法。你觉得怎么样?

type Env struct{
	db string // 应该是数据库,这只是一个例子
}

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

}

// 如果需要,可以根据不同的数据库创建不同的路由器。
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
		}
		// 运行测试
		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
		}
		// 运行测试
		return nil
	})
}
英文:

That's my idea. What do you think?

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
	})
}

答案2

得分: 1

如果您需要在测试套件的整个生命周期中使用单个路由器,并且由于无法更改指针接收器或重新注册路由处理程序,您可以尝试包装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) // 可以稍后更改`Fn`
    }
}

使用方法如下:

// 设置
router := mux.NewRouter()
wh := wrapperHandler{} //稍后填充wh.Fn
router.HandleFunc("/foo", wh.HandlerFunc())

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

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

请注意,这只是一个示例代码,您需要根据您的实际需求进行适当的修改。

英文:

If you need a single router throughout the lifetime of your test suite - and since you cannot change the pointer receiver or re-register route handlers - you could try wrapping the HandlerFunc where you can change the handler function later:

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
	}
}

to use:

// 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)

huangapple
  • 本文由 发表于 2021年9月18日 10:59:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/69231240.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定