Mocking HTTP Endpoints in Golang

I recently ran into a situation where I wanted to test an application that talked to a REST API and I wanted to mock out the server interaction. I couldn’t find many existing solutions in this space so I used httptest and testify to come up with a simple solution.

tl;dr – Point your client at a server created with httptest, capture and control interactions with the mock server per test, use testify’s suite functionality to reset the state per test.

First is a struct to hold the state for the test suite. This holds our fixture under test (Client), the mock API endpoint (Server), info about the last request to the mock endpoint  (LastRequest, LastRequestBody) and a hook to control the response from the current test (ResponseFunc).

import (
        "testing"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
)

type MyTestSuite struct {
	suite.Suite
	Client *MyClient
	Server          *httptest.Server
	LastRequest     *http.Request
	LastRequestBody string
	ResponseFunc    func(http.ResponseWriter, *http.Request)
}

The mock server is setup to update the suite state on each request and shut down when the suite is done.

func (s *MyTestSuite) SetupSuite() {
	s.Server = httptest.NewTLSServer(
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			body, _ := ioutil.ReadAll(r.Body)
			s.LastRequestBody = string(body)
			s.LastRequest = r
			if s.ResponseFunc != nil {
				s.ResponseFunc(w, r)
			}
		}))
}

func (s *MyTestSuite) TearDownSuite() {
	s.Server.Close()
}

func (s *MyTestSuite) SetupTest() {
	s.ResponseFunc = nil
	s.LastRequest = nil
}

func TestMySuite(t *testing.T) {
	suite.Run(t, new(MyTestSuite))
}

Each test method can now easily mock and assert each request.

func (s *MyTestSuite) TestGetById() {
	//setup
	s.ResponseFunc = func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"id": 0, "name": "Hugh Jass"}`))
	}

	//test
	name := s.Client.GetPerson(0)

	//verify
	assert.Equal(s.T(), name, "Hugh Jass")
	assert.Equal(s.T(), "/people/0", s.LastRequest.URL.Path)
}

func (s *MyTestSuite) TestCreate() {
	//test
	s.Client.CreatePerson("Han Solo")

	//verify
	assert.JSONEq(s.T(), `{"name":"Amanda Hugenkiss"}`, s.LastRequestBody)
	assert.Equal(s.T(), "/people", s.LastRequest.URL.Path)
	assert.Equal(s.T(), "POST", s.LastRequest.Method)
}

You can see an example of the end product in action for real here. Resetting mocks is a bit of a code smell so if I were to do this again I might look into creating an API similar to Spring’s MockRestServiceServer.

Hope it helps! Happy testing.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s