Skip to main content

Echo Mock Requests

Introduction

When developing web applications with Echo, testing is a crucial part of ensuring your code works correctly. During testing, you often need to simulate HTTP requests to your API endpoints without actually making real network calls or hitting external services. This is where mock requests come into play.

Mock requests allow you to create controlled testing environments where you can simulate HTTP requests to your Echo application and verify that your handlers respond correctly. This approach is especially valuable for unit tests, where you want to isolate the code being tested from external dependencies.

In this tutorial, we'll explore how to create and use mock requests in Echo testing, providing you with the tools to effectively test your Echo applications.

Understanding Mock Requests in Echo

In Echo testing, mock requests are created using the httptest package from the standard library along with Echo's own testing utilities. The basic process involves:

  1. Creating a new Echo instance
  2. Defining a mock request with specific HTTP method, URL, and optional body
  3. Recording the response using a response recorder
  4. Asserting that the response matches expected values

Mock requests allow you to test your handlers in isolation without starting a server or making real HTTP calls.

Setting Up Testing Environment

Before diving into mock requests, let's set up a proper testing environment:

go
package handlers_test

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

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

"your-project/handlers"
)

The import list includes Echo, the standard HTTP testing packages, and the commonly used assert package to make our test assertions more readable.

Creating Your First Mock Request

Let's start with a simple example. Suppose we have an Echo handler that returns a greeting message:

go
// handlers/greeting.go
package handlers

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

func Greeting(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}

Now, let's write a test for this handler using mock requests:

go
// handlers/greeting_test.go
func TestGreeting(t *testing.T) {
// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewResponseRecorder()
c := e.NewContext(req, rec)

// Call the handler
if assert.NoError(t, handlers.Greeting(c)) {
// Assert
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, "Hello, World!", rec.Body.String())
}
}

In this example:

  • We create a new Echo instance
  • We create a mock HTTP GET request to "/"
  • We create a response recorder to capture the response
  • We create a new Echo context with our request and recorder
  • We call the handler directly with this context
  • Finally, we assert that the response status and body match our expectations

Testing with Request Bodies

For endpoints that accept request bodies (like POST, PUT requests), we need to include data in our mock requests:

go
func TestCreateUser(t *testing.T) {
// JSON body
userJSON := `{"name":"John","email":"[email protected]"}`

// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodPost, "/users",
strings.NewReader(userJSON))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewResponseRecorder()
c := e.NewContext(req, rec)

// Call the handler
if assert.NoError(t, handlers.CreateUser(c)) {
// Assert
assert.Equal(t, http.StatusCreated, rec.Code)
assert.Contains(t, rec.Body.String(), "user created")
}
}

This example simulates sending JSON data to an endpoint, which you would commonly do in REST APIs.

Testing Path and Query Parameters

To test handlers that use path or query parameters, we need to set them in our context:

go
func TestGetUserById(t *testing.T) {
// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/users/123", nil)
rec := httptest.NewResponseRecorder()
c := e.NewContext(req, rec)
c.SetPath("/users/:id")
c.SetParamNames("id")
c.SetParamValues("123")

// Call the handler
if assert.NoError(t, handlers.GetUserById(c)) {
// Assert
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), "123")
}
}

For query parameters, we can include them in the request URL:

go
func TestSearchUsers(t *testing.T) {
// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/users?name=john&limit=10", nil)
rec := httptest.NewResponseRecorder()
c := e.NewContext(req, rec)

// Call the handler
if assert.NoError(t, handlers.SearchUsers(c)) {
// Assert
assert.Equal(t, http.StatusOK, rec.Code)
// Assert response body contains expected data
}
}

Testing with Headers and Cookies

For endpoints that require specific headers or cookies, you can add them to your mock request:

go
func TestAuthenticatedEndpoint(t *testing.T) {
// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/protected", nil)

// Add Authorization header
req.Header.Set("Authorization", "Bearer mock-token-123")

// Add a cookie
cookie := &http.Cookie{
Name: "session",
Value: "session-value",
}
req.AddCookie(cookie)

rec := httptest.NewResponseRecorder()
c := e.NewContext(req, rec)

// Call the handler
if assert.NoError(t, handlers.ProtectedResource(c)) {
// Assert
assert.Equal(t, http.StatusOK, rec.Code)
}
}

Mocking Middleware

Testing middleware requires a slightly different approach. You need to create a dummy handler that the middleware will wrap:

go
func TestAuthMiddleware(t *testing.T) {
// Setup
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set("Authorization", "Bearer valid-token")
rec := httptest.NewResponseRecorder()
c := e.NewContext(req, rec)

// Create a dummy handler to pass to the middleware
dummyHandler := func(c echo.Context) error {
return c.String(http.StatusOK, "reached the handler")
}

// Wrap the dummy handler with your middleware
h := middleware.RequireAuth(dummyHandler)

// Call the wrapped handler
err := h(c)

// Assert
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, "reached the handler", rec.Body.String())
}

This approach lets you verify that your middleware functions correctly allow or block requests as expected.

Real-world Example: Testing a RESTful API

Let's put everything together with a more complete example of testing a RESTful API endpoint:

go
func TestProductAPI(t *testing.T) {
// Setup
e := echo.New()

// Test case 1: Get all products
t.Run("GetAllProducts", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/products", nil)
rec := httptest.NewResponseRecorder()
c := e.NewContext(req, rec)

if assert.NoError(t, handlers.GetProducts(c)) {
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), "products")
}
})

// Test case 2: Create a product
t.Run("CreateProduct", func(t *testing.T) {
productJSON := `{"name":"New Product","price":29.99,"category":"electronics"}`
req := httptest.NewRequest(http.MethodPost, "/products",
strings.NewReader(productJSON))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewResponseRecorder()
c := e.NewContext(req, rec)

if assert.NoError(t, handlers.CreateProduct(c)) {
assert.Equal(t, http.StatusCreated, rec.Code)
assert.Contains(t, rec.Body.String(), "created")
}
})

// Test case 3: Get product by invalid ID
t.Run("GetProductByInvalidID", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/products/invalid-id", nil)
rec := httptest.NewResponseRecorder()
c := e.NewContext(req, rec)
c.SetPath("/products/:id")
c.SetParamNames("id")
c.SetParamValues("invalid-id")

err := handlers.GetProductByID(c)

// Our handler should return an error for invalid ID
assert.Error(t, err)

// If using Echo's HTTPErrorHandler, check recorded status
assert.Equal(t, http.StatusBadRequest, rec.Code)
})
}

This example demonstrates how to organize tests for related endpoints using subtests, making your test suite more readable and maintainable.

Best Practices for Echo Mock Requests

  1. Test in isolation: Each test should be independent and not rely on other tests.

  2. Use table-driven tests for testing multiple similar cases:

    go
    func TestValidateUserHandler(t *testing.T) {
    testCases := []struct {
    name string
    payload string
    statusCode int
    response string
    }{
    {
    name: "Valid user",
    payload: `{"name":"John","email":"[email protected]"}`,
    statusCode: http.StatusOK,
    response: "valid user",
    },
    {
    name: "Missing name",
    payload: `{"email":"[email protected]"}`,
    statusCode: http.StatusBadRequest,
    response: "name is required",
    },
    {
    name: "Invalid email",
    payload: `{"name":"John","email":"not-an-email"}`,
    statusCode: http.StatusBadRequest,
    response: "invalid email",
    },
    }

    for _, tc := range testCases {
    t.Run(tc.name, func(t *testing.T) {
    e := echo.New()
    req := httptest.NewRequest(http.MethodPost, "/validate",
    strings.NewReader(tc.payload))
    req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
    rec := httptest.NewResponseRecorder()
    c := e.NewContext(req, rec)

    handlers.ValidateUser(c)

    assert.Equal(t, tc.statusCode, rec.Code)
    assert.Contains(t, rec.Body.String(), tc.response)
    })
    }
    }
  3. Use helper functions to reduce boilerplate code in your tests:

    go
    func setupTest(method, url string, body io.Reader) (echo.Context, *httptest.ResponseRecorder) {
    e := echo.New()
    req := httptest.NewRequest(method, url, body)
    req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
    rec := httptest.NewResponseRecorder()
    return e.NewContext(req, rec), rec
    }
  4. Test both happy paths and edge cases:

    • Success scenarios
    • Missing or invalid parameters
    • Unauthorized access
    • Server errors
  5. Use meaningful assertions that help clarify what's expected.

Summary

Echo mock requests provide a powerful way to test your Echo application without making actual HTTP calls. Through these tests, you can verify that your handlers:

  • Return the correct status codes
  • Send appropriate response bodies
  • Handle query parameters and path variables correctly
  • Process request bodies properly
  • Apply middleware as expected

By using mock requests in your testing strategy, you can build more robust Echo applications with confidence that your API works as expected under various conditions.

Additional Resources

Exercises

  1. Create a mock request test for an Echo handler that returns a list of items.
  2. Write a test for an Echo handler that accepts POST requests with JSON data and validates the input.
  3. Implement tests for a middleware that checks authentication tokens.
  4. Create a complete test suite for a RESTful API with CRUD operations.
  5. Extend your tests to handle file uploads by creating mock multipart form requests.

By practicing these exercises, you'll become more comfortable with Echo testing and be able to ensure your applications are robust and reliable.



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