Skip to main content

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:

go
// 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:

go
// 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:

bash
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:

bash
go test ./handler -coverprofile=coverage.out

Step 5: Analyzing Coverage Results

View coverage statistics in the terminal:

bash
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:

bash
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:

go
// 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:

go
// 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:

bash
go test ./... -cover

For a detailed report:

bash
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:

yaml
# .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

  1. Aim for realistic coverage targets: 100% coverage is often impractical. Aim for 70-80% as a good baseline.

  2. Focus on critical paths: Ensure business-critical code has higher coverage.

  3. Test error handling: Make sure error cases are covered, not just happy paths.

  4. Don't just test for coverage: Write meaningful tests that validate behavior, not just execute code.

  5. Test middleware: Echo middleware is crucial to application behavior and should be well-tested.

  6. Test route registration: Ensure your routes are properly registered.

  7. Use table-driven tests: For similar handlers with different inputs.

go
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

Exercises

  1. Create a new Echo handler for a product resource and achieve at least 90% code coverage for it.

  2. Add middleware to an Echo application that checks for authentication and write tests that achieve 100% coverage for this middleware.

  3. Take an existing Echo application and generate a coverage report. Identify areas with low coverage and improve them.

  4. Set up a GitHub Actions workflow that runs tests with coverage reporting for an Echo project.

  5. 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! :)