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.