Table of Contents
- Introduction
- Prerequisites
- Setting Up the Project
- Using the
net/http/httptest
Package - Mocking HTTP Requests with
net/http/httptest
- Testing API Interactions
- Conclusion
Introduction
In this tutorial, we will learn how to test Go code that interacts with an external API. Testing is an essential part of software development, and when working with APIs, it’s crucial to have reliable tests to ensure that our code behaves as expected.
By the end of this tutorial, you will be able to write unit tests for code that relies on external API calls without making actual network requests. We will use the net/http/httptest
package to create a mock server and simulate API responses.
Prerequisites
To follow this tutorial, you should have a basic understanding of the Go programming language and be familiar with writing Go functions. You should also have Go installed on your machine.
Setting Up the Project
Before we dive into testing, let’s set up a project structure. Create a new directory for your project and navigate to it in the terminal. Initialize a new Go module by running the following command:
go mod init <module-name>
Replace <module-name>
with the desired name for your module.
Next, create a new Go file named api.go
and import the necessary packages:
package main
import (
"fmt"
"net/http"
)
// Your API-related code goes here...
func main() {
// Your main function implementation...
}
Great! Now we’re ready to start testing our code.
Using the net/http/httptest
Package
The net/http/httptest
package provides a simple way to test HTTP handlers in Go without making actual network requests. It allows us to create a mock server, make HTTP requests to it, and inspect the responses.
Let’s add our first test case to the api_test.go
file:
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestHandler(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
res := httptest.NewRecorder()
handler(res, req)
if res.Code != http.StatusOK {
t.Errorf("expected status code %d, got %d", http.StatusOK, res.Code)
}
}
In this example, we create a new HTTP GET request using httptest.NewRequest
and a new recorder to capture the response using httptest.NewRecorder
. Then, we call our handler
function (place the relevant handler code here) passing in the response and request objects.
Finally, we check if the response status code matches our expected value. If not, we log an error using t.Errorf
.
To run the test, execute the following command:
go test -v
You should see the output from the test case, indicating whether it passed or failed.
Mocking HTTP Requests with net/http/httptest
Now that we know how to create a mock server, let’s see how we can mock HTTP requests to external APIs.
In this example, we will create a simple function that retrieves data from a hypothetical weather API. We will mock the API response to ensure consistent testing.
First, let’s define our function in api.go
:
// FetchWeather returns the current weather for a given location.
func FetchWeather(location string) (string, error) {
// Make an HTTP request to the weather API and parse the response...
return "Sunny", nil
}
To test this function, we need to create a mock server that simulates the weather API. Modify the api_test.go
file as follows:
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestFetchWeather(t *testing.T) {
expectedWeather := "Sunny"
// Create a mock server that returns our expected weather
server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
fmt.Fprint(res, expectedWeather)
}))
// Replace the weather API endpoint with our mock server URL
apiEndpoint = server.URL
defer server.Close()
// Test the FetchWeather function
weather, err := FetchWeather("Seattle")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if weather != expectedWeather {
t.Errorf("expected weather %q, got %q", expectedWeather, weather)
}
}
In this example, we create a new mock server using httptest.NewServer
. We pass a function to the http.HandlerFunc
to define the server behavior. In this case, we simply write our expected weather to the response.
We also replace the API endpoint in our code with the mock server URL to ensure the API calls are directed to the mock server.
Finally, we test the FetchWeather
function and check if the returned weather matches our expectation.
Testing API Interactions
When testing code that interacts with an external API, it’s important to cover different scenarios, such as error handling and edge cases.
Let’s add another test case to cover the scenario where the API returns an error:
func TestFetchWeather_Error(t *testing.T) {
expectedError := "API error"
// Create a mock server that returns an error
server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(res, expectedError)
}))
apiEndpoint = server.URL
defer server.Close()
// Test the FetchWeather function
_, err := FetchWeather("Seattle")
if err == nil {
t.Errorf("expected error %q, got nil", expectedError)
}
if err.Error() != expectedError {
t.Errorf("expected error %q, got %q", expectedError, err.Error())
}
}
In this test case, we set up the mock server to return a response with a status code indicating an error. We also write the expected error message to the response body.
Next, we test the FetchWeather
function and verify that it returns an error matching our expectation.
Conclusion
In this tutorial, we learned how to test Go code that interacts with an external API. We explored the net/http/httptest
package, which allows us to create mock servers and simulate API responses.
By mocking HTTP requests and responses, we can thoroughly test our code without relying on the availability or stability of external APIs. This enables us to write reliable tests that cover different scenarios and edge cases.
Now that you have a good understanding of testing Go code with external API interactions, you can apply these techniques to your own projects and ensure the reliability of your code.
Remember to continue exploring the Go testing documentation to enrich your testing skills and unlock more possibilities for writing reliable and maintainable software.