Search code examples
gomux

How to Dynamically Change Method Receiver


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.


Solution

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