A Rubyist Learning Go – Testing HTTP

July 26, 2013 Mike Gehard

Welcome back to our semi-regularly scheduled program. This time around, we are going to take a look at how to write tests in Go, specifically those for http.Handler endpoints. If you are a Rails programmer, think of these handler endpoints as controller actions.

Background

Testing is built into the Go standard library in the form on the testing package. For those Rubyists that use Test::Unit/Minitest, the syntax will look familiar. I won’t go into the details but here is a list of resources to get you started:

http://golang.org/doc/code.html#Testing

http://golang.org/doc/faq#Packages_Testing

The built in testing framework lack assertions so at Pivotal we have been using a library called Testify to get some nice assertions. The tests below will use Testify. If you the Github repo with the code (https://github.com/msgehard/goBlogArticles) will have files for out of the box Go testing style and Testify.

The Problem

The problem we are trying to solve is we want to write a web server that will echo back whatever is sent as a “say” query param. (groundbreaking, I know…) We will do this using tests to drive out the design.

Below are two tests that confirm that the handler responds properly:

func TestEchosContent (t *testing.T) {
    expectedBody := "Hello"
    handler := new(EchoHandler)
    recorder := httptest.NewRecorder()
    url := fmt.Sprintf("http://example.com/echo?say=%s", expectedBody)
    req, err := http.NewRequest("GET", url, nil)
    assert.Nil(t, err)

    handler.ServeHTTP(recorder, req)

    assert.Equal(t, expectedBody, recorder.Body.String())
}

func TestReturns404IfYouSayNothing (t *testing.T) {
    handler := new(EchoHandler)
    recorder := httptest.NewRecorder()
    url := "http://example.com/echo?say=Nothing"
    req, err := http.NewRequest("GET", url, nil)
    assert.Nil(t, err)

    handler.ServeHTTP(recorder, req)

    assert.Equal(t, 404, recorder.Code)
}

The nice thing about Go http.Handlers is that they are functions that can be called directly, unlike Rails controller actions, and thus unit tested more easily. In this test we create the necessary arguments to ServeHTTP, specifically something that implements the http.ResponseWriter interface (in this case an httptest.ResponseRecorder) and an http.Request object. We then make assertions against the recorder to prove that our handler did what we wanted.

This ability to test the handlers directly is one of my favorite things about the http Go library.

Testing Client Libraries

Our next task is to implement a client library that interacts with our service. In the interest of time, I am going to use the http.Client struct to stand in for a library that hides more of the details of the API. When we write these types of libraries, we typically want to test against a mock API server. Go makes this very easy through the use of an httptest.Server.

Here is our client test:

func TestClient (t *testing.T) {
    server := httptest.NewServer(new(EchoHandler))
    defer server.Close()

    // Pretend this is some sort of Go client...
    url := fmt.Sprintf("%s?say=Nothing", server.URL)

    resp, err := http.DefaultClient.Get(url)
    assert.Nil(t, err)

    assert.Equal(t, 404, response.StatusCode)
}

Here we fire up a test server using our newly tested EchoHandler and then use an http.Client to test the API. The defer keyword allows us to declare our “cleanup” code, in this case stopping the test server, immediately after we perform the action we need to clean up from. This call will run immediately after the function returns and is a great substitute for the begin/rescue/ensure construct we are used to in Ruby.

This ease of setting up servers, for both testing and production (using http.ListenAndServe), makes writing client libraries for APIs a breeze and is another one of my favorite things about Go.

I hope this post gave you a basic understanding of how to test HTTP endpoints and and clients. In our next post, I plan to introduce some of the concurrency ideas that make Go so appealing as a language for concurrent programming.

About the Author

Biography

Previous
Using resolvconf.conf to Tweak resolv.conf
Using resolvconf.conf to Tweak resolv.conf

Abstract FreeBSD 9.1, when a DHCP client, uses resolvconf to construct /etc/resolv.conf (which defines the ...

Next
Ember vs Angular – Templates
Ember vs Angular – Templates

Over the last couple of weeks I’ve been fortunate enough to play around in both Ember and Angular. Having s...