'in tests mux.Vars() returns nil instead of expected map

I have a Gorilla Mux router setup inside a function that returns *mux.Router, goes like this

func MakeApp(services service.Service, pe PolicyEnforce) *mux.Router {
    app := mux.NewRouter()

    app.NotFoundHandler = &NotFound{}

    app.Use(token.TokenMiddleware)

    # ...
    
    app.Methods(http.MethodPost).Path("/api/v1/subscription/{emisor}/mh").HandlerFunc(MakeUpdateMH(services, pe))
    app.Methods(http.MethodGet).Path("/api/v1/subscription/{emisor}/mh").HandlerFunc(MakeGetMH(services, pe))
    app.Methods(http.MethodPost).Path("/api/v1/subscription").HandlerFunc(MakeCreateSubscription(services, pe))
    app.Methods(http.MethodGet).Path("/api/v1/subscription/{emisor}").HandlerFunc(MakeGetSubscription(services, pe))
    
    # ...

    return app
}

So, on my tests I prepare the handle with an URL and run it:

func (suite *AppTestSuite) TestUpdateMH() {
    args := &service.UpdateMHInput{
        Usuario:     new(string),
        Clave:       new(string),
        Pin:         new(string),
        Certificado: new(string),
        Actividades: []string{},
    }
    reader, err := makeBody(args)
    suite.NoError(err)

    handle := token.TokenMiddleware(transport.MakeUpdateMH(suite._s, suite.pe))

    req := httptest.NewRequest(http.MethodPut, "/api/v1/subscription/-/mh", reader)
    w := httptest.NewRecorder()

    t := genToken([]v43.Rol{
        {
            Nombre: "mh",
            Permisos: []v43.Permiso{{
                Sujeto: permission.MHCredentials,
                Accion: permission.Update,
            }},
        },
    })
    req.Header.Add("Authorization", t)

    // configura repository
    suite.r.On("UpdateIssuerMH", emisor, args.Usuario, args.Clave, args.Pin, args.Certificado, args.Actividades).Return(v43.Grupo{}, nil)
    handle.ServeHTTP(w, req)

    resp := w.Result()

    suite.Equal(http.StatusOK, resp.StatusCode, resp.Status)
}

Inside the handle things look like this:

func MakeUpdateMH(s service.Service, p PolicyEnforce) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        emisor := mux.Vars(r)["emisor"]

        // revisa permisos
        permitido := p.TienePermiso(r.Context(), permission.MHCredentials, permission.Update) && p.PuedeActuarSobreEmisor(r.Context(), emisor)
        if !permitido {
            reportError(w, fault.ErrPermissionDenied, http.StatusForbidden, fault.MessageForbidden, fault.MessageForbidden)
            return
        }
        // cambia el emisor afectado por aquel obtenido de la URL
        if emisor != "-" || emisor == "" {
            emisor = token.GetSub(r.Context())
        }

        var input service.UpdateMHInput
        dec := json.NewDecoder(r.Body)
        err := dec.Decode(&input)
        if err != nil {
            http.Error(w, fault.NewBackendError("no se pudo decodificar solicitud: %v", err).Error(), http.StatusBadRequest)
            return
        }

        output, err := s.UpdateMHCredentials(emisor, input.Usuario, input.Clave, input.Pin, input.Certificado, input.Actividades)
        if err != nil {
            http.Error(w, fault.NewBackendError("Error al actualizar credenciales de MH: %v", err).Error(), http.StatusInternalServerError)
            return
        }

        enc := json.NewEncoder(w)
        enc.Encode(output)
    }
}

and I've noticed that mux.Vars(r) is returning nil instead of the map of values, which should contain {"emisor": "-"} and I cannot understand why this is not the case.

I'm already handling the case for when "emisor" is empty, but for other routers where "-" or empty string cannot be used this quirk is causing me troubles, what am I doing wrong, and how can I still run my tests successfully without having to manually inject my Vars? and also: will this issue translate into production?



Solution 1:[1]

My setup is wrong, I'm not using *mux.Router in my tests but calling the handlers directly. If I wanted to use the *mux.Router returned by my function MakeApp then I'll need to put that inside a test HTTP server using net/http/httptest.

Solution 2:[2]

If you're creating a request that you're not routing but you still want the Vars, there's another option.

router := mux.NewRouter()

... // setup your routes

var match mux.RouteMatch
success := router.Match(req, &match)
if success {
  id := match.Vars["id"]
  ...
}

This is equivalent to router.ServeHTTP(req, res) in how it'll prep the variables except it doesn't actually execute the handler. The match object also has more information about the route that the request would hit.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 shackra
Solution 2 Corey Ogburn