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:
# 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:
- Create a new request
- Select the HTTP method (GET, POST, PUT, DELETE)
- Enter your URL (e.g.,
http://localhost:1323/users
) - Add headers if needed
- Add request body for POST/PUT requests
- 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:
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:
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:
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:
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:
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:
Scenario | Expected Status Code |
---|---|
Successful GET | 200 OK |
Successful POST | 201 Created |
Successful DELETE | 204 No Content |
Bad Request | 400 Bad Request |
Unauthorized | 401 Unauthorized |
Forbidden | 403 Forbidden |
Not Found | 404 Not Found |
Server Error | 500 Internal Server Error |
Real-World Example: Complete Todo API Testing
Here's a more comprehensive example of testing a Todo API built with Echo:
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
- Echo Testing Documentation
- Go Testing Package Documentation
- Testify Assertion Library
- Postman Learning Center
Exercises
- Write tests for a simple Echo API that has endpoints to create, read, update, and delete user records.
- Create tests that verify your API handles invalid input correctly.
- Write integration tests that test the full flow of your API, including database operations.
- Implement and test rate limiting middleware.
- 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! :)