Unit Testing with Ginkgo: Part 3

Utkarsh Mani Tripathi
3 min readDec 29, 2019

In the previous blog we have learned to test http handlers at the server side. In this blog we will learn how to test the same from the client side, commonly known as mocking http servers in this blog. Ginkgo framework provides very interesting package ghttp which is wrapper over httptest package and makes the unit testing great again 😜.

If you have not visited my previous two blogs and not familiar with the ginkgo framework, it is recommended to go through them first. Here we are going to write the client code for the http server explained in the previous blog.

package mainimport (
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
)
func main() {
url := "http://localhost:8080/read"
body, err := getResponse(url)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
func getResponse(url string) ([]byte, error) {if len(url) == 0 {
return nil, errors.New("Invalid URL")
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
c := &http.Client{
Timeout: 5 * time.Second,
}
resp, err := c.Do(req)if err != nil {
return nil, err
}
defer resp.Body.Close()code := resp.StatusCode
body, err := ioutil.ReadAll(resp.Body)
if err == nil && code != http.StatusOK {
return nil, fmt.Errorf(string(body))
}
if code != http.StatusOK {
return nil, fmt.Errorf("Server status error: %v", http.StatusText(code))
}
return body, nil
}

This is very simple client code which is sending Get request to the http server and error cases have been handled accordingly.

Here is the test code where we test our getResponse function. In the test code i’m starting mock server and mocking the response and response code in each of the test cases. RespondWithPtr function takes the expected response code, expected body and optional header (which i’m ignoring). There are other functions like RespondWithJSONEncodedPtr as well which returns the response in json format upon request to http server.

package mainimport (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/ghttp"
)
var _ = Describe("Client", func() {var (
server *ghttp.Server
statusCode int
body []byte
path string
addr string
)
BeforeEach(func() {
// start a test http server
server = ghttp.NewServer()
})
AfterEach(func() {
server.Close()
})
Context("When given empty url", func() {
BeforeEach(func() {
addr = ""
})
It("Returns the empty path", func() {
_, err := getResponse(addr)
Expect(err).Should(HaveOccurred())
})
})
Context("When given unsupported protocol scheme", func() {
BeforeEach(func() {
addr = "tcp://localhost"
})
It("Returns the empty path", func() {
_, err := getResponse(addr)
Expect(err).Should(HaveOccurred())
})
})
Context("When get request is sent to empty path", func() {
BeforeEach(func() {
statusCode = 200
path = "/"
body = []byte("Hi there, the end point is :!")
addr = "http://" + server.Addr() + path
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", path),
ghttp.RespondWithPtr(&statusCode, &body),
))
})
It("Returns the empty path", func() {
bdy, err := getResponse(addr)
Expect(err).ShouldNot(HaveOccurred())
Expect(bdy).To(Equal(body))
})
})
Context("When get request is sent to hello path", func() {
BeforeEach(func() {
statusCode = 200
path = "/hello"
body = []byte("Hi there, the end point is :hello!")
addr = "http://" + server.Addr() + path
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", path),
ghttp.RespondWithPtr(&statusCode, &body),
))
})
It("Returns the hello path", func() {
bdy, err := getResponse(addr)
Expect(err).ShouldNot(HaveOccurred())
Expect(bdy).To(Equal(body))
})
})
Context("When get request is sent to read path but there is no file", func() {
BeforeEach(func() {
statusCode = 500
path = "/read"
body = []byte("open data.txt: no such file or directory\r\n")
addr = "http://" + server.Addr() + path
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", path),
ghttp.RespondWithPtr(&statusCode, &body),
))
})
It("Returns internal server error", func() {
_, err := getResponse(addr)
Expect(err).Should(HaveOccurred())
})
})
Context("When get request is sent to read path but file exists", func() {
BeforeEach(func() {
file, err := os.Create("data.txt")
Expect(err).NotTo(HaveOccurred())
body = []byte("Hi there!")
file.Write(body)
statusCode = 200
path = "/read"
addr = "http://" + server.Addr() + path
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", path),
ghttp.RespondWithPtr(&statusCode, &body),
))
})
AfterEach(func() {
err := os.Remove("data.txt")
Expect(err).NotTo(HaveOccurred())
})
It("Reads data from file successfully", func() {
bdy, err := getResponse(addr)
Expect(err).ShouldNot(HaveOccurred())
Expect(bdy).To(Equal(body))
})
})
})

Now let’s pray 🙏 and run the test to see the result 😜.

This is the simplest way to test you http clients using Ginkgo. There are other tips and tricks as well to test the uncovered branches using interfaces and monkey patching which i will cover in the next blog. Feel free to comment if you have found any issue and stay tuned for my next blog.

That’s all folks ! As always thanks for reading. 😊

~ उत्कर्ष_उवाच

--

--