Enhancing API Test Structure with Go's Testing Package

Enhancing API Test Structure with Go's Testing Package

Welcome to the second lesson of the course Automating API Tests with Go. In this lesson, we will build on the concepts introduced in the first lesson by enhancing our API test structure using Go’s testing package.

As API test suites grow, maintaining clarity, consistency, and efficiency becomes critical. Well-structured tests improve maintainability, readability, and scalability, ensuring that tests remain effective over time.

What You’ll Learn in This Lesson

By the end of this lesson, you will be able to:

  • Structure API tests using helper functions for reusable logic.
  • Use subtests to organize related test cases.
  • Leverage table-driven tests to test multiple scenarios efficiently.

Refactoring Tests with Helper Functions

In the previous lesson, we wrote a basic API test that verified the /todos endpoint. However, as we add more tests, duplicating logic—such as making HTTP requests and parsing responses—can make tests harder to maintain. Helper functions allow us to extract reusable logic.

Let's refactor our test by introducing a helper function for making API requests:

package main_test

import (
    "encoding/json"
    "net/http"
    "testing"
)

const baseURL = "http://localhost:8000"

// Helper function to make GET requests and parse the JSON response
func getJSONResponse(t *testing.T, url string, target interface{}) {
    t.Helper()
    response, err := http.Get(url)
    if err != nil {
        t.Fatalf("Failed to make GET request: %v", err)
    }
    defer response.Body.Close()

    if response.StatusCode != http.StatusOK {
        t.Fatalf("Expected status code 200, got %d", response.StatusCode)
    }

    if err := json.NewDecoder(response.Body).Decode(target); err != nil {
        t.Fatalf("Failed to decode response body: %v", err)
    }
}

func TestGetAllTodos(t *testing.T) {
    // Arrange
    url := baseURL + "/todos"

    // Act & Assert
    var todos []map[string]interface{}
    getJSONResponse(t, url, &todos)

    if len(todos) == 0 {
        t.Error("Expected non-empty list of todos.")
    }

    // Basic contract validation
    if _, ok := todos[0]["id"]; !ok {
        t.Error("Expected field 'id' in response")
    }
    if _, ok := todos[0]["title"]; !ok {
        t.Error("Expected field 'title' in response")
    }
}

The t.Helper() function marks a function as a helper, ensuring that if a test fails inside it, the error message points to the actual test function rather than the helper itself. This improves debugging by making test failures easier to trace. Without t.Helper(), errors may appear to originate from the helper function, making it harder to identify which test actually failed. It should always be used at the beginning of helper functions that perform assertions or can cause test failures.

This refactoring reduces duplication and makes our tests cleaner and more maintainable.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal