- Published on
http client testing in go
- Authors
- Name
- Zain Hasan
Frequently, we develop software that needs to communicate with a service through the vast web of networks. This service might be a database, a web server, or even a faraway file system. Ensuring that our code is thoroughly tested and addresses all reasonable use cases is essential to good programming practice. However, testing code that initiates a network call can prove to be a tad tricky.
When our test reaches out to a remote service, a myriad of issues might crop up. The distant service could be down, inaccessible, or acting in a way that catches us off guard. Suddenly, we find ourselves in a digital dilemma. As a programmer, I strive to create unit tests that thoroughly examine crucial aspects of my code, all without fretting over a pesky remote issue that's entirely beyond my control.
Integration tests certainly have their time and place. They offer immense value when it comes to evaluating how our code meshes with other components of our architecture, and whether it delivers the goods when part of the grand digital machine. Running these tests in a CI environment seems like a reasonable approach. But, of course, there's a catch. Integration tests can be quite the investment, so it's crucial to weigh the pros and cons. We need to determine which tests offer the biggest bang for our buck, considering the costs involved. Ultimately, it's all about striking that perfect balance and getting the most value from our testing efforts.
Throughout my coding journey, I've encountered numerous situations where making a remote network call was essential. When crafting tests, I typically leaned on a trusty mocking framework. For instance, in Java, I've employed Mockito to mimic method calls that led to network connections. And if I needed to put the local networking stack through its paces, Wiremock was my go-to tool. Ultimately, I stumbled upon the fantastic library called Testcontainers, which allowed me to effortlessly spin up disposable containers to test remote services—a true game-changer in my testing repertoire! However, it's worth noting that the advancement of these tools also brings along extra complexity and a learning curve.
To craft efficient unit tests with extensive coverage, simplicity is key. Utilizing the language and tools already at your disposal can make a significant difference. As a delightful bonus, you'll wind up with easy-to-comprehend, idiomatic code.
While learning go, I discovered a neat package tucked into the standard go library, net/http/httptest.
httptest
provides utilities for testing your HTTP stack. Lets take a quick look at the example code:
// https://pkg.go.dev/net/http/httptest#example-Server
package main
import (
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
)
func main() {
// Create a test server
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, client")
}))
defer ts.Close()
// Issue an HTTP GET request to the test server
res, err := http.Get(ts.URL)
if err != nil {
log.Fatal(err)
}
greeting, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", greeting)
}
Thanks to the Golang team. There might be similar testing utility gems tuckd in the Go standard library. I will cover some more in a future post.