Golang: Http Testing Case ( gorilla/mux )

In our projects we have a lot of tests. Http Api tests are one of the most important parts of any testing.
Error Messages, Status Codes and Responses should be tested very careful.

This article contains:

  • Preparations
  • Test case
  • The magic function
  • The test
  • Conclusion

We will use a github.com/gorilla/mux for url routing.

Preparations

We are going to test a User handler. A handler which responses with a User profile.

func User(w http.ResponseWriter, r *http.Request) {  
    ...
}

The router:

import (  
    "github.com/gorilla/mux"
)

func NewRouter() (s *mux.Router) {  
    s = mux.NewRouter()

    userHandler := s.PathPrefix("/user/").Subrouter()
    userHandler.HandleFunc("/", UserRegister).Methods("POST").Name("UserRegister")
    userHandler.HandleFunc("/{user_id}/", User).Methods("GET", "HEAD").Name("User")

    return s
}

Test case

In our test case we need to start a httptest.NewServer which will use a router:

import (  
    ...
    "net/http/httptest"
    "net/url"
    "testing"
    ...
)

func TestUserRegistration(t *testing.T) {  
    router := NewRouter()
    localServer := httptest.NewServer(router)
    defer localServer.Close()
    localServerUrl, err := url.Parse(localServer.URL)

    ...
}

We've got a localServerUrl which points to our endpont.

The magic function

We need to form a url for committing a request. Let's write a function for it

import (  
    "github.com/gorilla/mux"
    "net/url"
)

func MakeUrl(server_url *url.URL, router *mux.Router, urlName string, url_params []string, query_params map[string]string) (*url.URL, error) {  
    //  Get url from routing
    urlPtr, err := router.Get(urlName).URL(
        url_params...,
    )
    if err != nil {
        return nil, err
    }

    //  Get currently serving httptest
    url_I := *server_url
    //  Join it's address and path of a specific url
    url_I.Path = urlPtr.Path
    q := url_I.Query()
    for k, v := range query_params {
        q.Set(k, v)
    }
    url_I.RawQuery = q.Encode()

    return &url_I, err
}

This function makes Just Do It with an url :D

To form a url for getting a user's profile you need to call it:

    url, err := MakeUrl(
        localServerUrl,
        router,
        "UserProfile",        // <- HANDLER's NAME
        []string{
            "user_id", 1234,  // <- HANDLER's URL PARAMS:   /users/123/
        },
        map[string]string{}{
            "format": "json", // <- HANDLER URL QUERY:      /users/123/?format=json
        }, 
    )
    Expect(err).NotTo(HaveOccurred())

Let's assume we have a complicated handler

    entityHandler.HandleFunc("/{entity_id}/{entity_property:(.*[\\\\/]?)}/", http_handlers.PutEntityProperty()).Methods("PUT", "OPTIONS").Name("PutEntityProperty")

To make a request you need to

    url, err := MakeUrl(
        localServerUrl,
        router,
        "PutEntityProperty",
        []string{
            "entity_id", "users",
            "entity_property": "email"
        },
        map[string]string{}{
        }, 
    )
    Expect(err).NotTo(HaveOccurred())

The test

Our test will look like

    email := generated_data["email"]
    url, err := test_helpers.MakeUrl(
        localServerUrl,
        router,
        "UserRegister",
        []string{},
        map[string]string{},
    )

    request := &TestHttpRequest{}
    err := request.Post(
        url.String(),
        map[string]interface{}{
            "Content-Type": "application/json"
        },
        map[string]interface{}{
            "email":    email,
            "password": generated_data["password"],
        },
    )

Conclusion

We have an easy http handlers testing. Just pass url's name, params and query params to function. In case if your endpoints will change - there is no need in editing a lot of tests to rename url :D