# Testing Strategies for Kratos Middleware Components and Applications

> Master Kratos middleware testing. Explore unit tests, mock handlers, integration, and end-to-end strategies for robust applications. Ensure reliability with Kratos.

- Repository: [Kratos/kratos](https://github.com/go-kratos/kratos)
- Tags: testing
- Published: 2026-03-02

---

**Kratos middleware can be validated through unit tests with mock handlers, chain composition tests, transport-level integration tests with in-process servers, and end-to-end tests using the full application bootstrap.**

The go-kratos/kratos framework implements middleware as pure Go functions that wrap a **handler**, enabling straightforward testing at multiple isolation levels. This guide covers the recommended testing strategies for Kratos middleware components and applications, from isolated logic verification to full integration suites.

## Understanding the Kratos Middleware Contract

At its core, Kratos middleware relies on two functional types defined in [`middleware/middleware.go`](https://github.com/go-kratos/kratos/blob/main/middleware/middleware.go):

```go
type Handler    func(ctx context.Context, req any) (any, error)
type Middleware func(Handler) Handler

```

A `Middleware` receives a `Handler` and returns a new `Handler` that may execute logic before, after, or around the inner handler. The `Chain` helper composes multiple middlewares into a single pipeline. According to the implementation in [`middleware/middleware.go`](https://github.com/go-kratos/kratos/blob/main/middleware/middleware.go), `Chain` applies middlewares in reverse order so the first element in the slice executes first during request processing.

## Unit Testing Kratos Middleware Components

Unit tests for individual middleware should isolate the component from transport details. The standard pattern involves creating a **mock handler** that records execution or returns deterministic responses, wrapping it with the middleware under test, and asserting on the side effects.

### Testing Individual Middleware with Mock Handlers

A robust unit test follows this structure:

1. Create a mock `Handler` that captures arguments or returns fixed values.
2. Apply the middleware using the constructor (e.g., `recovery.Recovery()`).
3. Invoke the resulting handler with a test context and request.
4. Assert that the middleware transformed the context, error, or response as expected.

#### Example: Testing the Recovery Middleware

The recovery middleware in [`middleware/recovery/recovery.go`](https://github.com/go-kratos/kratos/blob/main/middleware/recovery/recovery.go) catches panics and converts them to errors. The corresponding test in [`middleware/recovery/recovery_test.go`](https://github.com/go-kratos/kratos/blob/main/middleware/recovery/recovery_test.go) demonstrates how to verify context propagation and error transformation:

```go
func TestRecoveryMiddleware(t *testing.T) {
    // Panic-inducing handler
    next := func(context.Context, any) (any, error) {
        panic("simulated panic")
    }

    // Recovery with custom handler to verify latency recording
    _, err := Recovery(WithHandler(func(ctx context.Context, _, r any) error {
        if _, ok := ctx.Value(Latency{}).(float64); !ok {
            t.Errorf("latency not recorded in context")
        }
        return errors.InternalServer("RECOVERY", fmt.Sprintf("panic: %v", r))
    }))(next)(context.Background(), "request")

    if err == nil {
        t.Fatal("expected error from panic recovery")
    }
}

```

This test verifies that the middleware catches the panic, records latency in the context, and returns a properly formatted internal server error.

#### Example: Testing the Validate Middleware

The validate middleware in [`middleware/validate/validate.go`](https://github.com/go-kratos/kratos/blob/main/middleware/validate/validate.go) checks if requests implement a `Validator` interface. The test in [`middleware/validate/validate_test.go`](https://github.com/go-kratos/kratos/blob/main/middleware/validate/validate_test.go) uses a mock handler that returns the request unchanged and asserts that validation errors are converted to `BadRequest` responses:

```go
func TestValidateMiddleware(t *testing.T) {
    next := func(ctx context.Context, req any) (any, error) {
        return req, nil
    }

    // Test with invalid request
    invalidReq := &MockValidator{valid: false}
    _, err := Validator()(next)(context.Background(), invalidReq)
    
    if !errors.Is(err, errors.BadRequest) {
        t.Fatalf("expected BadRequest error, got %v", err)
    }
}

```

## Testing Middleware Chains

When multiple middlewares are composed, you should verify that they execute in the correct order and that context values propagate through the chain. The `Chain` helper in [`middleware/middleware.go`](https://github.com/go-kratos/kratos/blob/main/middleware/middleware.go) applies middlewares in reverse order, so the first middleware in the slice is the outermost wrapper.

The test in [`middleware/middleware_test.go`](https://github.com/go-kratos/kratos/blob/main/middleware/middleware_test.go) demonstrates this by creating three dummy middlewares that increment a shared counter and verifying that the final count reflects the execution order:

```go
func TestMiddlewareChain(t *testing.T) {
    var counter int
    makeMiddleware := func(id int) middleware.Middleware {
        return func(next middleware.Handler) middleware.Handler {
            return func(ctx context.Context, req any) (any, error) {
                counter += id
                return next(ctx, req)
            }
        }
    }

    // Chain applies in reverse: m1(m2(m3(next)))
    chain := middleware.Chain(makeMiddleware(1), makeMiddleware(10), makeMiddleware(100))
    handler := chain(func(ctx context.Context, req any) (any, error) {
        return "done", nil
    })

    handler(context.Background(), nil)
    // Expected: 1 + 10 + 100 = 111
    if counter != 111 {
        t.Fatalf("expected counter 111, got %d", counter)
    }
}

```

## Integration Testing with Transport Layers

To verify that middleware functions correctly within the HTTP or gRPC transport pipelines, use the transport-specific test utilities. Kratos transports expose a `WithMiddleware` option that injects middleware into the server pipeline.

### HTTP Transport Testing

The HTTP transport tests in [`transport/http/client_test.go`](https://github.com/go-kratos/kratos/blob/main/transport/http/client_test.go) demonstrate how to verify middleware execution in an in-process server. You can spin up an HTTP server with middleware configured, register a service implementation, and call it through the client stub:

```go
func TestHTTPMiddlewareIntegration(t *testing.T) {
    // Service implementation
    svc := &testService{}
    
    // Create server with middleware
    srv := http.NewServer(
        http.Address(":0"),
        http.Middleware(
            middleware.Chain(
                recovery.Recovery(),
                validate.Validator(),
            ),
        ),
    )
    
    // Register service and start
    pb.RegisterTestServiceServer(srv, svc)
    go srv.Start()
    defer srv.Stop()
    
    // Create client
    client := pb.NewTestServiceClient(
        http.NewClient(http.WithEndpoint(srv.Endpoint())),
    )
    
    // Execute request and verify middleware effects
    resp, err := client.TestMethod(context.Background(), &pb.TestRequest{})
    if err != nil {
        t.Fatalf("request failed: %v", err)
    }
    // Assert response...
}

```

### gRPC Transport Testing

The same pattern applies to gRPC transports using `transport/grpc`. Configure middleware via the server options and use the generated gRPC client to verify behavior across the network boundary.

## End-to-End Testing for Kratos Applications

For comprehensive validation, test the complete application bootstrap using `kratos.New`. This approach verifies that middleware is correctly wired into the application lifecycle, context propagation works across transport layers, and the internal matcher ([`internal/matcher/middleware.go`](https://github.com/go-kratos/kratos/blob/main/internal/matcher/middleware.go)) respects middleware ordering.

```go
func TestAppEndToEnd(t *testing.T) {
    // Build complete application
    app := kratos.New(
        kratos.Name("test-app"),
        kratos.Metadata(map[string]string{"env": "test"}),
        kratos.Server(
            http.NewServer(
                http.Address(":0"),
                http.Middleware(
                    middleware.Chain(
                        recovery.Recovery(),
                        logging.Logging(logger),
                        validate.Validator(),
                    ),
                ),
            ),
        ),
    )
    
    // Start application in background
    go func() { _ = app.Run() }()
    defer app.Stop()
    
    // Allow server to start
    time.Sleep(100 * time.Millisecond)
    
    // Perform real HTTP calls
    client := http.NewClient(
        http.WithEndpoint(fmt.Sprintf("http://%s", app.Endpoint())),
    )
    
    // Test valid request
    resp, err := client.Invoke(context.Background(), "GET", "/test", nil)
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    
    // Verify middleware effects (logging output, headers, etc.)
    // ...
}

```

This end-to-end approach confirms that middleware ordering is preserved by the framework's internal matcher and that context values propagate correctly through the entire request lifecycle.

## Common Pitfalls and How to Avoid Them

When testing Kratos middleware, avoid these frequent mistakes:

| Pitfall | Why It Matters | Solution |
|---------|----------------|----------|
| **Missing context value assertions** | Middleware often stores latency, tracing IDs, or metadata in context that tests fail to verify. | Always assert on context values after the handler returns, as demonstrated in the `Recovery` test. |
| **Incorrect chain order expectations** | The `Chain` helper applies middlewares in reverse order; tests expecting forward execution will fail. | Use the `Chain` implementation as the source of truth and verify side-effects match the reverse application order. |
| **Real network dependencies in unit tests** | Using actual network calls slows CI and introduces flakiness. | Stick to in-process transport tests or mock the transport layer entirely. |
| **String-based error comparison** | Kratos error types carry metadata; string comparison ignores error codes and details. | Use `errors.Is()` or `errors.Code()` for proper error validation. |

## Summary

Testing Kratos middleware components and applications requires a layered approach that leverages the framework's functional design:

- **Unit tests** isolate individual middleware using mock handlers to verify logic, error transformation, and context manipulation.
- **Chain tests** validate middleware ordering and interaction using the `Chain` helper with shared state assertions.
- **Integration tests** verify transport-layer wiring by spinning up in-process HTTP or gRPC servers with middleware configured via `WithMiddleware`.
- **End-to-end tests** bootstrap complete applications using `kratos.New` to ensure middleware behaves correctly across the full request lifecycle.

By combining these strategies and avoiding common pitfalls like incorrect chain order assumptions or missing context assertions, you can ensure your Kratos middleware performs reliably in production environments.

## Frequently Asked Questions

### How do I test middleware that modifies context values?

To test context modification, create a mock handler that retrieves values from the context after the middleware executes. In your test, invoke the wrapped handler and then assert that the context contains the expected keys and values. For example, when testing the Recovery middleware, verify that the `Latency` value is stored in the context by accessing it within a custom recovery handler or by checking context values passed to subsequent handlers.

### What is the correct order for testing middleware chains?

When testing chains, remember that the `Chain` helper in [`middleware/middleware.go`](https://github.com/go-kratos/kratos/blob/main/middleware/middleware.go) applies middlewares in reverse order, meaning the first middleware in the slice becomes the outermost wrapper. To verify correct ordering, create middlewares that increment a shared counter or append identifiers to a slice, then assert that the side effects occur in the reverse order of the slice index. This ensures your tests align with the actual execution flow implemented by the framework.

### How do I perform integration testing without real network calls?

Use the in-process server capabilities provided by the HTTP and gRPC transports. Create a server using `http.NewServer` or `grpc.NewServer` with `http.Address(":0")` (which assigns a random available port), register your service, and start the server in a goroutine. Then create a client using the server's actual endpoint (`srv.Endpoint()`) to perform real RPC calls through the middleware stack without external network dependencies or port conflicts.

### How do I verify panic recovery in middleware tests?

To test panic recovery, create a mock handler that deliberately calls `panic()` with a specific message. Wrap this handler with the Recovery middleware configured with a custom handler function that captures the panic reason and context values. Invoke the wrapped handler and assert that the returned error contains the expected recovery message and that no panic escapes the test. Verify that context values like latency are properly recorded even during panic scenarios by checking them within the custom recovery handler.