How to Test Go Code with an External API

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Setting Up the Project
  4. Using the net/http/httptest Package
  5. Mocking HTTP Requests with net/http/httptest
  6. Testing API Interactions
  7. 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.