July 22, 2014

In this blog post, I am going to show you how I tested my HTTP API in Go.

The endpoints

Before jumping to the test code, I want to show you the two endpoints that I am testing

  • POST /users to create user
  • GET /users to list users

You can go to the full source code by clicking here, but I am going to take you through the code snippet here as well. Feel free to skip to the next section if you are comfortable with the code.

Like many Go applications, the starting point for this web app is the main function. It listens to port 3000 and handles the 2 endpoints mentioned above. Along with the main function, in api.go, I also have userStore that will serve as a storage when a new user is created and userIdCounter that will increment when a new user is successfully created.

//Inside main.go
func main() {
    fmt.Println("Server starting")
    http.ListenAndServe(":3000", api.Handlers())
}
//Inside api.go
var userIdCounter uint32 = 0

var userStore = []User{}

type User struct {
    Id           uint32 `json:"id"`
    Username     string `json:"username"`
    MoneyBalance uint32 `json:"balance"`
}

func Handlers() *mux.Router{
    r := mux.NewRouter()

    r.HandleFunc("/users", createUserHandler).Methods("POST")

    r.HandleFunc("/users", listUsersHandler).Methods("GET")

    return r
}

I am using Gorilla Mux to route my request. If you have never seen this package before, definitely check it out. It has tools that will make your life easier when building web application.

Next up is the POST /users endpoint for creating user. The handler function for this endpoint is createUserHandler. In it, I am calling validateUniqueness to ensure that username is unique across all users. If validation passes, I create a new user and increments the ID counter.

func createUserHandler(w http.ResponseWriter, r *http.Request) {
    p := UserParams{}

    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        fmt.Printf("Error: %s\n", err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    err = json.Unmarshal(body, &p)
    if err != nil {
        fmt.Printf("Error: %s\n", err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    err = validateUniqueness(p.Username)

    if err != nil {
        fmt.Printf("Error: %s\n", err)
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    u := User{
        Id:           userIdCounter,
        Username:     p.Username,
        MoneyBalance: p.MoneyBalance,
    }

    userStore = append(userStore, u)

    userIdCounter += 1

    w.WriteHeader(http.StatusCreated)
}

func validateUniqueness(username string) error {
    for _, u := range userStore {
        if u.Username == username {
            return errors.New("Username is already used")
        }
    }

    return nil
}

The last endpoint is GET /users which lists all the users in userStore.

func listUsersHandler(w http.ResponseWriter, r *http.Request) {
    users, err := json.Marshal(userStore)

    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    w.Write(users)
}

If you want to run this web app, please checkout the github README.

The tests

There are several cases that I want to test here.

  • Successful user creation
  • Error when creating user witn non-unique username
  • Successful listing of user records in JSON

Note that this is not comprehensive. I would definitely take more time to consider other cases that might break your endpoints e.g. broken JSON.

Setting up the server

You can think of testing HTTP endpoints in Go as starting the HTTP server, sending request to the endpoint and making some assertions about the response. This is what we are going to do.

Under the api package, you want to create a new file called api_test.go. In it, create an init function that will create a new HTTP server for testing and get the user URL. Remember to import all the necessary packages.

package api_test

import (
    "api"
    "fmt"
    "io"
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"
)

var (
    server   *httptest.Server
    reader   io.Reader //Ignore this for now
    usersUrl string
)

func init() {
    server = httptest.NewServer(api.Handlers()) //Creating new server with the user handlers

    usersUrl = fmt.Sprintf("%s/users", server.URL) //Grab the address for the API endpoint
}

Try running the test.

go test src/api/api_test.go

There should be no error.

ok  	command-line-arguments	0.019s

Now we are going to test the success case for creating user. To do this, we are going to:

  • Create an HTTP request with JSON body

  • POST it to the endpoint we create in the init function

  • and check to make sure that the HTTP response status code is 201

If this expectation is not met, we want to error. And, here is the code.

func TestCreateUser(t *testing.T) {
    userJson := `{"username": "dennis", "balance": 200}`

    reader = strings.NewReader(userJson) //Convert string to reader

    request, err := http.NewRequest("POST", usersUrl, reader) //Create request with JSON body

    res, err := http.DefaultClient.Do(request)

    if err != nil {
        t.Error(err) //Something is wrong while sending request
    }

    if res.StatusCode != 201 {
        t.Errorf("Success expected: %d", res.StatusCode) //Uh-oh this means our test failed
    }
}

That’s it. Everything is manual. You might find Go testing framework kind of lacking especially if you come from Ruby/Rails background where stubbing/mocking is the norm. I actually kind of enjoy it because everything is explicit and there isn’t that much magic going on.

I was going to write down the other two tests but I guess I will leave it as an exercise. As always, you can go to the source code and see how I did it.

Thanks for reading!

Simple Slide Out Navigation Menu in iOS with Swift (Part 2)

Continuing from part 1, we are going to see how to switch the displayed view controller when one of the menu selections is tapped Continue reading

Subdomain with NGINX on Linode

Published on January 14, 2015