Mocking API's in Golang... for fun AND profit!

Software development in 2016 is a crazy complex process. Modern applications are increasingly distributed, regularly requiring access to an array of systems controlled by 3rd, 4th and 5th parties. So what happens when we are building the next killer-app-uber-disrupting-unicorn and the API that we NEED to access goes down?

Slam our Macbooks shut, whine about up-time and go home?
Consume our body weight in caffeine in preparation for the glorious return of the API?
Sit quietly and wait for morning to come?

All of those are solid choices, but I think mocking out the API endpoint in our local dev environment is the way to go. Or at least, the one that allows us to keep our productivity up during down time.

Enter Golang a.k.a. Go!!
I've been dabbling with Go for a few months now and I've found it to be a quick and easy way to spin up apps. The following post assumes basic software/web development knowledge, and although Golang knowledge helps it is not required.

I chose Go for a few reasons, the main being its excellent standard library. Nothing in this post will require any dependencies outside the standard library. Another would be the ease and speed of building and distributing binaries. This is supposed to be a tool that enables us to get what we need to done, not distract with extraneous work.

First, lets spin up a webserver:

package main
import (
	"log"
	"net/http"
)
func main() {
	log.Fatal(http.ListenAndServe(":8080", nil))
}

That's the only code we need for a fully functioning web server.

Save the code to a file, say api-mocks.go, we can run it using:
go run api-mocks.go

Next navigate to localhost:8080 in your browser and you should be met with a 404 error if your server is running successfully. The 404 here is expected because we haven't defined any routes or resources for our server to serve. If you're server wasn't running you wouldn't get the 404, the browser would display an error message specifying it couldn't connect to the server.

Let's get a little fancier. We can use Go's http library to define some handler functions that will handle our request so we can get something besides a 404.

func main() {
	http.HandleFunc("/goats-hello", func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Write([]byte(`Hello Goats!`))
	})
 
	log.Fatal(http.ListenAndServe(":8080", nil))
}

We still see our original call to http.ListenAndServer(), but now we also have defined a handler function using http.HandleFunc(). http.handleFunc() takes two parameters, a string of the URI we would like to define and the second is the function to handle the request. This anonymous function takes two parameters, a http.ResponseWriter that will write out the HTTP response and a pointer to an http.Request. Save the changes and run go run api-mocks.go then point your browser to http://localhost:8080/goats-hello. You should see "Hello Goats!"

Few lines. Very wow.
Next we'll make it do something a little more interesting. Pretend we are building an app called GoatBase that aggregates goat-based statistics for visualization. GoatBase relies on a 3rd party goat tracking service API called "Bah-Trac". You set aside a whole day to work on GoatBase only to find out that Bah-Trac will be down for the next 48 hours. We want to hit the /goat-location endpoint and we know what the JSON structure we are expecting looks. We are expecting an array of objects, each object representing a goat, and it's last known coordinates. The code to mock that API looks a little like this:

func main() {
 
	goatJson := []byte(`{
                "result": [{
			"id": 11111,
			"lat": "38.898648N",
			"lon": "77.037692W"
		},
		{
			"id": 22222,
			"lat": "45.5233371N",
			"lon": "98.4587345E"
		},
		{
			"id": 33333,
			"lat": "66.6666666S",
			"lon": "49.2333333W"
		},
		{
			"id": 44444,
			"lat": "6.55555555N",
			"lon": "89.9999999W"
		}
	]}`)
 
	http.HandleFunc("/goats-hello", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`Hello Goats!`))
	})
 
	http.HandleFunc("/goats-location", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(goatJson))
	})
 
	log.Fatal(http.ListenAndServe(":8080", nil))
}

We've added another endpoint, /goats-location that returns some JSON.

Save the changes and rerun go run api-mocks.go. Point your browser at http://localhost:8080/goats-location. You should see the goatsJson that we defined in api-mocks.go dumped out to the browser.

Now that we have a functioning endpoint, all we need to do is change the call in GoatBase from pointing to the real Bah-Trac endpoint, to localhost:8080/goat-location.

Pretty cool! That's all for now, but in my next posts I'll dive into the specifics of Golang environment configuration and demonstrate how to write some unit tests for these new endpoints.

Comments

Why not use ts := httptest.NewServer()
then ts.URL()?

Hello!

Great suggestion, you certainly could do that. I'll definitely be getting into the httptest package in later posts.

-Matt

Hi! You dont need that last []byte(goatJson) conversion, since goatJson is already a []byte :D

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.

Ready for transformation?