Skip to main content

Echo API Testing

Testing is a critical part of API development that ensures your endpoints function correctly and reliably. This guide will walk you through various approaches to testing Echo APIs, from simple manual tests to automated testing frameworks.

Introduction to API Testing

API testing involves sending requests to your API endpoints and verifying that the responses match your expectations. For an Echo API, we need to validate:

  • Response status codes
  • Response body content
  • Headers
  • Performance metrics
  • Error handling

Good testing practices help catch bugs early, ensure consistent behavior, and provide confidence when refactoring or adding new features.

Manual Testing Tools

Using curl

curl is a command-line tool for transferring data with URLs. It's perfect for quick API tests:

bash
# Testing a GET endpoint
curl http://localhost:1323/users

# Testing a POST endpoint with JSON data
curl -X POST -H "Content-Type: application/json" -d '{"name": "John", "email": "[email protected]"}' http://localhost:1323/users

Using Postman

Postman provides a graphical interface for API testing:

  1. Create a new request
  2. Select the HTTP method (GET, POST, PUT, DELETE)
  3. Enter your URL (e.g., http://localhost:1323/users)
  4. Add headers if needed
  5. Add request body for POST/PUT requests
  6. Send the request and examine the response

Postman also allows you to save requests, create collections, and even automate tests with scripts.

Automated Testing in Go

Unit Testing Echo Handlers

Echo makes it easy to write unit tests for your handlers using Go's built-in testing package and Echo's test utilities.

Here's a basic example of testing a simple Echo handler:

go
package handlers

import (
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)

func TestHelloHandler(t *testing.T) {
// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/hello", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

// Assertions
if assert.NoError(t, HelloHandler(c)) {
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, "Hello, World!", rec.Body.String())
}
}

func TestUserCreateHandler(t *testing.T) {
// Setup
e := echo.New()
jsonBody := `{"name":"John Doe","email":"[email protected]"}`
req := httptest.NewRequest(http.MethodPost, "/users", strings.NewReader(jsonBody))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

// Assertions
if assert.NoError(t, CreateUserHandler(c)) {
assert.Equal(t, http.StatusCreated, rec.Code)
assert.Contains(t, rec.Body.String(), "John Doe")
assert.Contains(t, rec.Body.String(), "[email protected]")
}
}

Integration Testing

Integration tests verify that your API works correctly with all components connected. Here's how to write an integration test for an Echo API:

go
func TestUserAPIIntegration(t *testing.T) {
// Setup server
e := setupEchoServer() // Your function that configures the Echo server
server := httptest.NewServer(e)
defer server.Close()

// Test GET users (initially empty)
resp, err := http.Get(server.URL + "/users")
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)

// Test POST to create a user
userJSON := `{"name":"Jane Smith","email":"[email protected]"}`
resp, err = http.Post(
server.URL+"/users",
"application/json",
strings.NewReader(userJSON),
)
assert.NoError(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)

// Test GET users again (should contain the new user)
resp, err = http.Get(server.URL + "/users")
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)

body, err := io.ReadAll(resp.Body)
assert.NoError(t, err)
assert.Contains(t, string(body), "Jane Smith")
}

Testing Best Practices

1. Test Request Validation

Echo provides built-in request validation. Test that your API properly validates input data:

go
func TestInvalidUserData(t *testing.T) {
e := echo.New()
e.Validator = setupValidator() // Your validator setup

// Invalid email
jsonBody := `{"name":"John Doe","email":"not-an-email"}`
req := httptest.NewRequest(http.MethodPost, "/users", strings.NewReader(jsonBody))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

// Call handler
CreateUserHandler(c)

// Should return validation error
assert.Equal(t, http.StatusBadRequest, rec.Code)
assert.Contains(t, rec.Body.String(), "validation failed")
}

2. Test Error Cases

Always test how your API handles errors:

go
func TestUserNotFound(t *testing.T) {
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetPath("/users/:id")
c.SetParamNames("id")
c.SetParamValues("999") // Non-existent ID

// Call handler
GetUserHandler(c)

// Should return not found
assert.Equal(t, http.StatusNotFound, rec.Code)
}

3. Test Middleware

Test that middleware functions correctly:

go
func TestAuthMiddleware(t *testing.T) {
e := echo.New()

// Test with no auth token
req := httptest.NewRequest(http.MethodGet, "/protected", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

h := AuthMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "You're in!")
})

// Should return unauthorized
h(c)
assert.Equal(t, http.StatusUnauthorized, rec.Code)

// Test with valid auth token
req = httptest.NewRequest(http.MethodGet, "/protected", nil)
req.Header.Set("Authorization", "Bearer valid-token")
rec = httptest.NewRecorder()
c = e.NewContext(req, rec)

h(c)
assert.Equal(t, http.StatusOK, rec.Code)
}

Testing HTTP Response Codes

Make sure your API returns appropriate HTTP status codes:

ScenarioExpected Status Code
Successful GET200 OK
Successful POST201 Created
Successful DELETE204 No Content
Bad Request400 Bad Request
Unauthorized401 Unauthorized
Forbidden403 Forbidden
Not Found404 Not Found
Server Error500 Internal Server Error

Real-World Example: Complete Todo API Testing

Here's a more comprehensive example of testing a Todo API built with Echo:

go
package api

import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)

type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}

func TestTodoAPI(t *testing.T) {
// Setup
e := echo.New()
e.POST("/todos", createTodoHandler)
e.GET("/todos", getAllTodosHandler)
e.GET("/todos/:id", getTodoHandler)
e.PUT("/todos/:id", updateTodoHandler)
e.DELETE("/todos/:id", deleteTodoHandler)

// Test creating a todo
todoJSON := `{"title":"Buy groceries","completed":false}`
req := httptest.NewRequest(http.MethodPost, "/todos", strings.NewReader(todoJSON))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)

assert.Equal(t, http.StatusCreated, rec.Code)

var createdTodo Todo
json.Unmarshal(rec.Body.Bytes(), &createdTodo)
assert.NotEqual(t, 0, createdTodo.ID)
assert.Equal(t, "Buy groceries", createdTodo.Title)
assert.False(t, createdTodo.Completed)

// Test getting all todos
req = httptest.NewRequest(http.MethodGet, "/todos", nil)
rec = httptest.NewRecorder()
e.ServeHTTP(rec, req)

assert.Equal(t, http.StatusOK, rec.Code)

var todos []Todo
json.Unmarshal(rec.Body.Bytes(), &todos)
assert.GreaterOrEqual(t, len(todos), 1)

// Test getting a single todo
todoID := createdTodo.ID
req = httptest.NewRequest(http.MethodGet, "/todos/"+string(todoID), nil)
rec = httptest.NewRecorder()
e.ServeHTTP(rec, req)

assert.Equal(t, http.StatusOK, rec.Code)

var todo Todo
json.Unmarshal(rec.Body.Bytes(), &todo)
assert.Equal(t, todoID, todo.ID)

// Test updating a todo
updateJSON := `{"title":"Buy groceries","completed":true}`
req = httptest.NewRequest(http.MethodPut, "/todos/"+string(todoID), strings.NewReader(updateJSON))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec = httptest.NewRecorder()
e.ServeHTTP(rec, req)

assert.Equal(t, http.StatusOK, rec.Code)

var updatedTodo Todo
json.Unmarshal(rec.Body.Bytes(), &updatedTodo)
assert.True(t, updatedTodo.Completed)

// Test deleting a todo
req = httptest.NewRequest(http.MethodDelete, "/todos/"+string(todoID), nil)
rec = httptest.NewRecorder()
e.ServeHTTP(rec, req)

assert.Equal(t, http.StatusNoContent, rec.Code)

// Verify todo is deleted
req = httptest.NewRequest(http.MethodGet, "/todos/"+string(todoID), nil)
rec = httptest.NewRecorder()
e.ServeHTTP(rec, req)

assert.Equal(t, http.StatusNotFound, rec.Code)
}

Summary

Thorough testing is essential for building reliable Echo APIs. We've covered:

  • Manual testing with curl and Postman
  • Unit testing Echo handlers
  • Integration testing of entire API flows
  • Testing best practices, including request validation and error handling
  • Testing middleware functions
  • A comprehensive real-world example

By incorporating these testing strategies into your development workflow, you'll create more robust, reliable, and maintainable Echo APIs.

Additional Resources

Exercises

  1. Write tests for a simple Echo API that has endpoints to create, read, update, and delete user records.
  2. Create tests that verify your API handles invalid input correctly.
  3. Write integration tests that test the full flow of your API, including database operations.
  4. Implement and test rate limiting middleware.
  5. Create a test suite that can be run as part of a CI/CD pipeline.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)