So I had to mock an API in something I'm writing for testing and I figured I'd post this to show you an easy way to do it in Go.
First you need to get the JSON structure for the response you'll be mocking. To do this you can just use cURL or whatever tool you want.
Let's use JSON Placeholder since it's free and doesn't require any authentication. If you go to this URL, you'll see the comments API: https://jsonplaceholder.typicode.com/posts/1/comments
I use this site to convert JSON to structs. If you paste in the JSON data you get a struct like this:
type Comment []struct {
PostID int `json:"postId"`
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Body string `json:"body"`
}
So we just need to be able to mock this in our unit tests. Here's a small app that reaches out to JSON Placeholder and gets all of the comments that exist for a specific post.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
type Comment []struct {
PostID int `json:"postId"`
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Body string `json:"body"`
}
func (c Comment) getComment(u string) {
req, err := http.Get(u)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(req.Body)
if err != nil {
log.Fatal(err)
}
error := json.Unmarshal(body, &c)
if error != nil {
log.Fatal(error)
}
fmt.Println(c[0].Name)
}
func main() {
var c Comment
c.getComment("https://jsonplaceholder.typicode.com/posts/1/comments")
}
However, when you're unit testing, you don't want to have to actually reach out to the site. That's more of an integration test. So to be able to test our method, we need to mock that API endpoint. So to do that Go has a test server built in for this reason. You can use it like this:
package main
import (
"io"
"net/http"
"net/http/httptest"
"testing"
)
type commentTest struct {
actual string
expected string
}
type commentInt struct {
actual int
expected int
}
func TestgetComment(t *testing.T) {
data := `
[
{
"postId": 1,
"id": 1,
"name": "test name",
"email": "[email protected]",
"body": "this is the body"
},
]
`
ts := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, data)
}))
defer ts.Close()
var c Comment
c.getComment(ts.URL)
var tests = []commentTest{
{c[0].Name, "test name"},
{c[0].Email, "[email protected]"},
{c[0].Body, "this is the body"},
}
for _, tt := range tests {
if tt.actual != tt.expected {
t.Errorf("got %v, want %v", tt.actual, tt.expected)
}
}
var testInt = []commentInt {
{c[0].PostID, 1},
{c[0].ID, 1},
}
for _, ti := range testInt {
if ti.actual != ti.expected {
t.Errorf("got %v, want %v", ti.actual, ti.expected)
}
}
}
This particular test happened to be a little long because of the two types of data. There might be an easier way by building one struct for both the int and string types but this way was easy to write really quickly.
I don't want to get into how testing in Go works. However, the important line is ts := httptest.NewServer()
. That's where we define our test server. We tell it to take data
and put that into w
which is our response. So when we run our c.getComment()
method we wrote earlier, we can pass ts.URL
which is the URL of our test server and we will get a response of whatever is in data
. Then we just compare the results to what we expect them to be and if they aren't the same the test will fail.
It's good to note that if your JSON data is long, you probably don't want to put it in line. So you could store it in a file alongside your code and pass the data in that way. You would just use something like data, err := ioutil.ReadFile("./data.json")
.