Skip to main content

Echo Test Fixtures

When testing Echo applications, you'll often find yourself repeating the same setup code across multiple tests. This is where test fixtures come into play. Test fixtures provide a way to set up consistent test environments, making your tests more readable, maintainable, and efficient.

What are Test Fixtures?

Test fixtures are fixed states or environments used as a baseline for running tests. They provide a known, fixed environment where tests can be run reliably and repeatedly. In the context of Echo, fixtures typically include:

  • Pre-configured Echo instances
  • Mock database connections
  • Sample request/response data
  • Authentication tokens
  • Any other resources needed for testing

Why Use Test Fixtures?

Before diving into the implementation, let's understand why fixtures are valuable:

  1. Reusability: Write setup code once, use it in multiple tests
  2. Consistency: Ensure all tests run in the same environment
  3. Readability: Keep test functions focused on what's being tested rather than setup
  4. Maintainability: When your setup needs change, update it in one place

Creating Basic Echo Test Fixtures

Let's start with a simple fixture that provides a configured Echo instance for testing:

go
package testing

import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"net/http/httptest"
"strings"
)

// EchoTestFixture provides a configured Echo instance for testing
type EchoTestFixture struct {
Echo *echo.Echo
}

// NewEchoTestFixture creates a new Echo test fixture
func NewEchoTestFixture() *EchoTestFixture {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())

return &EchoTestFixture{
Echo: e,
}
}

// PerformRequest performs a test HTTP request and returns the response
func (f *EchoTestFixture) PerformRequest(method, path string, body string) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, path, strings.NewReader(body))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)

rec := httptest.NewResponseRecorder()
f.Echo.ServeHTTP(rec, req)

return rec
}

Using the Basic Fixture

Here's how you can use this fixture in your tests:

go
package handler_test

import (
"testing"
"github.com/stretchr/testify/assert"
"yourproject/testing"
"yourproject/handler"
)

func TestHealthCheck(t *testing.T) {
// Setup fixture
fixture := testing.NewEchoTestFixture()

// Register the handler being tested
fixture.Echo.GET("/health", handler.HealthCheck)

// Perform the request
resp := fixture.PerformRequest("GET", "/health", "")

// Assert results
assert.Equal(t, 200, resp.Code)
assert.Equal(t, `{"status":"ok"}`, resp.Body.String())
}

Advanced Test Fixtures with Database

Many Echo applications interact with databases. Let's create a more advanced fixture that includes a test database connection:

go
package testing

import (
"database/sql"
"github.com/labstack/echo/v4"
_ "github.com/mattn/go-sqlite3"
"log"
)

// DBTestFixture provides an Echo instance and database connection for testing
type DBTestFixture struct {
Echo *echo.Echo
DB *sql.DB
}

// NewDBTestFixture creates a test fixture with an in-memory SQLite database
func NewDBTestFixture() *DBTestFixture {
e := echo.New()

// Create an in-memory SQLite DB for testing
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
log.Fatalf("Failed to open database: %v", err)
}

// Set up schema
_, err = db.Exec(`
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
`)
if err != nil {
log.Fatalf("Failed to create schema: %v", err)
}

return &DBTestFixture{
Echo: e,
DB: db,
}
}

// SeedUsers adds test users to the database
func (f *DBTestFixture) SeedUsers() error {
_, err := f.DB.Exec(`
INSERT INTO users (name, email) VALUES
('Test User', '[email protected]'),
('Admin User', '[email protected]')
`)
return err
}

// Cleanup closes the database connection
func (f *DBTestFixture) Cleanup() {
if f.DB != nil {
f.DB.Close()
}
}

Using the Database Fixture

Here's how to use the database fixture in a test:

go
package handler_test

import (
"testing"
"github.com/stretchr/testify/assert"
"yourproject/testing"
"yourproject/handler"
)

func TestGetUsers(t *testing.T) {
// Setup fixture
fixture := testing.NewDBTestFixture()
defer fixture.Cleanup() // Ensure cleanup happens

// Seed test data
err := fixture.SeedUsers()
assert.NoError(t, err)

// Create handler with DB dependency
userHandler := handler.NewUserHandler(fixture.DB)

// Register the handler
fixture.Echo.GET("/users", userHandler.GetUsers)

// Perform the request
resp := fixture.PerformRequest("GET", "/users", "")

// Assert results
assert.Equal(t, 200, resp.Code)
assert.Contains(t, resp.Body.String(), "Test User")
assert.Contains(t, resp.Body.String(), "Admin User")
}

Fixture with Authentication

For testing protected routes, we need fixtures that handle authentication. Here's an example:

go
package testing

import (
"github.com/labstack/echo/v4"
"github.com/golang-jwt/jwt/v4"
"time"
)

// AuthTestFixture provides Echo instance with auth capabilities
type AuthTestFixture struct {
Echo *echo.Echo
JWTSecret string
}

// NewAuthTestFixture creates a fixture with JWT authentication
func NewAuthTestFixture() *AuthTestFixture {
e := echo.New()
jwtSecret := "test-secret-key"

return &AuthTestFixture{
Echo: e,
JWTSecret: jwtSecret,
}
}

// GenerateToken creates a JWT token for test authentication
func (f *AuthTestFixture) GenerateToken(userID string, isAdmin bool) (string, error) {
// Create token
token := jwt.New(jwt.SigningMethodHS256)

// Set claims
claims := token.Claims.(jwt.MapClaims)
claims["id"] = userID
claims["admin"] = isAdmin
claims["exp"] = time.Now().Add(time.Hour * 1).Unix()

// Generate encoded token
tokenString, err := token.SignedString([]byte(f.JWTSecret))
if err != nil {
return "", err
}

return tokenString, nil
}

// PerformAuthenticatedRequest performs a request with JWT token
func (f *AuthTestFixture) PerformAuthenticatedRequest(method, path string, body string, token string) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, path, strings.NewReader(body))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
req.Header.Set(echo.HeaderAuthorization, "Bearer "+token)

rec := httptest.NewResponseRecorder()
f.Echo.ServeHTTP(rec, req)

return rec
}

Using the Auth Fixture

Here's how to test a protected route:

go
package handler_test

import (
"testing"
"github.com/stretchr/testify/assert"
"yourproject/testing"
"yourproject/handler"
"yourproject/middleware"
)

func TestProtectedRoute(t *testing.T) {
// Setup fixture
fixture := testing.NewAuthTestFixture()

// Configure JWT middleware
jwtMiddleware := middleware.JWT([]byte(fixture.JWTSecret))

// Create and register handler
adminHandler := handler.NewAdminHandler()
adminGroup := fixture.Echo.Group("/admin")
adminGroup.Use(jwtMiddleware)
adminGroup.GET("/dashboard", adminHandler.GetDashboard)

// Test case 1: With valid admin token
token, err := fixture.GenerateToken("1", true) // Admin user
assert.NoError(t, err)

resp := fixture.PerformAuthenticatedRequest("GET", "/admin/dashboard", "", token)
assert.Equal(t, 200, resp.Code)

// Test case 2: With non-admin token
regularToken, err := fixture.GenerateToken("2", false) // Regular user
assert.NoError(t, err)

resp = fixture.PerformAuthenticatedRequest("GET", "/admin/dashboard", "", regularToken)
assert.Equal(t, 403, resp.Code) // Forbidden

// Test case 3: Without token
resp = fixture.PerformRequest("GET", "/admin/dashboard", "")
assert.Equal(t, 401, resp.Code) // Unauthorized
}

Best Practices for Echo Test Fixtures

To get the most out of your test fixtures:

  1. Keep fixtures focused: Create different fixtures for different testing needs
  2. Clean up resources: Always clean up resources like database connections
  3. Use interfaces: Design fixtures to work with interfaces rather than concrete implementations
  4. Provide helper methods: Add methods to make common testing tasks easier
  5. Avoid global state: Keep fixture state isolated to prevent test interference

Combining Fixtures with Table-Driven Tests

Fixtures work especially well with table-driven tests in Go:

go
func TestUserAPI(t *testing.T) {
// Setup fixture once
fixture := testing.NewDBTestFixture()
defer fixture.Cleanup()
fixture.SeedUsers()

// Register handlers
userHandler := handler.NewUserHandler(fixture.DB)
fixture.Echo.GET("/users/:id", userHandler.GetUser)
fixture.Echo.PUT("/users/:id", userHandler.UpdateUser)

// Table-driven test cases
testCases := []struct {
name string
method string
path string
body string
expectedStatus int
expectedBody string
}{
{
name: "Get existing user",
method: "GET",
path: "/users/1",
body: "",
expectedStatus: 200,
expectedBody: "Test User",
},
{
name: "Get non-existent user",
method: "GET",
path: "/users/999",
body: "",
expectedStatus: 404,
expectedBody: "not found",
},
// More test cases...
}

// Execute all test cases
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resp := fixture.PerformRequest(tc.method, tc.path, tc.body)
assert.Equal(t, tc.expectedStatus, resp.Code)
assert.Contains(t, resp.Body.String(), tc.expectedBody)
})
}
}

Summary

Test fixtures in Echo provide a powerful way to set up consistent, reliable testing environments. By extracting common setup code into reusable fixtures, you can make your tests more maintainable and focused on what's actually being tested rather than setup details.

In this guide, we've covered:

  • Creating basic Echo test fixtures
  • Building database-enabled fixtures
  • Setting up fixtures with authentication
  • Combining fixtures with table-driven tests
  • Best practices for fixture design

By adopting test fixtures in your Echo applications, you'll create a more maintainable test suite that can grow with your application.

Additional Resources

Exercises

  1. Create a test fixture that includes both database and authentication capabilities
  2. Extend the DB fixture to support transaction-based tests (with rollback)
  3. Build a fixture for testing file uploads in Echo
  4. Create a mock HTTP client fixture for testing external API interactions
  5. Design a fixture that supports testing websocket endpoints in Echo


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