Echo Code Coverage
Code coverage is a critical metric in software testing that measures how much of your code is executed during test runs. For Echo applications, tracking code coverage helps identify untested code paths, ensuring your web applications remain robust and reliable. This guide will walk you through setting up and analyzing code coverage for your Echo applications.
What is Code Coverage?
Code coverage is a measurement of how many lines, statements, branches, or functions in your source code are executed during test runs. High code coverage indicates that your tests are thoroughly examining your code, which can lead to higher quality and more reliable applications.
For Echo applications written in Go, code coverage is particularly valuable as it helps ensure your HTTP handlers, middleware, and routing logic are properly tested.
Setting Up Code Coverage in Echo Applications
Go includes built-in code coverage tools that work seamlessly with Echo applications. Let's explore how to set them up.
Step 1: Writing Testable Echo Code
Before testing for coverage, let's ensure our Echo code is structured for testability. Here's a simple Echo handler:
// handler/user.go
package handler
import (
"net/http"
"github.com/labstack/echo/v4"
)
type UserHandler struct {
// Dependencies could go here
}
func NewUserHandler() *UserHandler {
return &UserHandler{}
}
func (h *UserHandler) GetUser(c echo.Context) error {
id := c.Param("id")
if id == "" {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "user id is required",
})
}
// In a real app, you would fetch from database
user := map[string]string{
"id": id,
"name": "John Doe",
"email": "[email protected]",
}
return c.JSON(http.StatusOK, user)
}
Step 2: Writing Tests for Echo Handlers
Next, let's write tests for our handler:
// handler/user_test.go
package handler
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
func TestGetUser(t *testing.T) {
// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/users/1", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetPath("/users/:id")
c.SetParamNames("id")
c.SetParamValues("1")
// Create handler
h := NewUserHandler()
// Test
if assert.NoError(t, h.GetUser(c)) {
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), "John Doe")
}
}
func TestGetUserWithMissingID(t *testing.T) {
// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/users/", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetPath("/users/:id")
c.SetParamNames("id")
c.SetParamValues("") // Empty ID
// Create handler
h := NewUserHandler()
// Test
if assert.NoError(t, h.GetUser(c)) {
assert.Equal(t, http.StatusBadRequest, rec.Code)
assert.Contains(t, rec.Body.String(), "user id is required")
}
}
Step 3: Running Tests with Coverage
Now that we have tests, we can run them with coverage enabled:
go test ./handler -cover
This command will run the tests and display the coverage percentage:
ok yourproject/handler 0.007s coverage: 92.3% of statements
Step 4: Generating a Coverage Profile
For more detailed analysis, generate a coverage profile:
go test ./handler -coverprofile=coverage.out
Step 5: Analyzing Coverage Results
View coverage statistics in the terminal:
go tool cover -func=coverage.out
Sample output:
yourproject/handler/user.go:15: NewUserHandler 100.0%
yourproject/handler/user.go:19: GetUser 92.3%
total: (statements) 94.1%
Or generate an HTML report with color-coded coverage:
go tool cover -html=coverage.out -o coverage.html
This creates an interactive HTML page where:
- Green lines are covered by tests
- Red lines have not been executed by tests
- Gray lines are non-executable (like comments)
Real-World Echo Coverage Example
Let's examine a more comprehensive example with multiple handlers and routes:
// main.go
package main
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"yourproject/handler"
)
func setupRoutes(e *echo.Echo) {
// Initialize handlers
userHandler := handler.NewUserHandler()
// Routes
e.GET("/users/:id", userHandler.GetUser)
e.POST("/users", userHandler.CreateUser)
e.PUT("/users/:id", userHandler.UpdateUser)
e.DELETE("/users/:id", userHandler.DeleteUser)
}
func main() {
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Setup routes
setupRoutes(e)
// Start server
e.Logger.Fatal(e.Start(":8080"))
}
To test the entire application, create a test file:
// main_test.go
package main
import (
"testing"
"net/http"
"net/http/httptest"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
func TestEndpoints(t *testing.T) {
// Create new Echo instance for testing
e := echo.New()
setupRoutes(e)
// Test GET /users/1
req := httptest.NewRequest(http.MethodGet, "/users/1", nil)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
// More endpoint tests...
}
Running Coverage for the Entire Application
To check coverage across all packages:
go test ./... -cover
For a detailed report:
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.html
Integrating Coverage into CI/CD
For continuous integration, add coverage checks to your workflow:
# .github/workflows/test.yml
name: Test with Coverage
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
- name: Test with coverage
run: go test ./... -coverprofile=coverage.out
- name: Check coverage
run: |
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | tr -d '%')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "Code coverage is below 80%: $COVERAGE%"
exit 1
fi
echo "Code coverage is acceptable: $COVERAGE%"
Best Practices for Echo Code Coverage
-
Aim for realistic coverage targets: 100% coverage is often impractical. Aim for 70-80% as a good baseline.
-
Focus on critical paths: Ensure business-critical code has higher coverage.
-
Test error handling: Make sure error cases are covered, not just happy paths.
-
Don't just test for coverage: Write meaningful tests that validate behavior, not just execute code.
-
Test middleware: Echo middleware is crucial to application behavior and should be well-tested.
-
Test route registration: Ensure your routes are properly registered.
-
Use table-driven tests: For similar handlers with different inputs.
func TestHandlers(t *testing.T) {
tests := []struct {
name string
path string
paramName string
paramValue string
expectedStatus int
expectedBody string
}{
{
name: "Valid user ID",
path: "/users/:id",
paramName: "id",
paramValue: "1",
expectedStatus: http.StatusOK,
expectedBody: "John Doe",
},
{
name: "Empty user ID",
path: "/users/:id",
paramName: "id",
paramValue: "",
expectedStatus: http.StatusBadRequest,
expectedBody: "user id is required",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetPath(tc.path)
if tc.paramName != "" {
c.SetParamNames(tc.paramName)
c.SetParamValues(tc.paramValue)
}
h := NewUserHandler()
_ = h.GetUser(c)
assert.Equal(t, tc.expectedStatus, rec.Code)
assert.Contains(t, rec.Body.String(), tc.expectedBody)
})
}
}
Summary
Code coverage is an essential metric for ensuring the quality and reliability of Echo applications. By following the steps outlined in this guide, you can effectively measure and improve the test coverage of your Echo handlers, middleware, and routing logic.
Remember that code coverage is just one aspect of testing. While high coverage is desirable, the quality and meaningfulness of your tests are equally important. Focus on testing the behavior of your application, not just achieving a high coverage percentage.
Additional Resources
- Go's Testing Package Documentation
- Echo Testing Guide
- Go Code Coverage Tools
- Test-Driven Development in Go
Exercises
-
Create a new Echo handler for a product resource and achieve at least 90% code coverage for it.
-
Add middleware to an Echo application that checks for authentication and write tests that achieve 100% coverage for this middleware.
-
Take an existing Echo application and generate a coverage report. Identify areas with low coverage and improve them.
-
Set up a GitHub Actions workflow that runs tests with coverage reporting for an Echo project.
-
Use table-driven tests to thoroughly test a route with multiple possible query parameters and response scenarios.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)