Embedded Go Server

Simfra can run in-process inside Go tests. No Docker, no external process, no port conflicts. Each test gets its own server instance with complete isolation.

Prerequisites

  • Go 1.21+
  • github.com/simfra-dev/simfra added as a dependency in your Go module

Basic Usage

package myapp_test

import (
    "context"
    "testing"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/credentials"
    "github.com/aws/aws-sdk-go-v2/service/sqs"

    simfra "github.com/simfra-dev/simfra/pkg/server"
    sqssvc "github.com/simfra-dev/simfra/internal/services/sqs"
)

func TestMyFeature(t *testing.T) {
    server := simfra.NewServer(sqssvc.New(nil, nil, nil))
    endpoint, cleanup := server.Start()
    defer cleanup()

    // Configure AWS SDK
    cfg, err := config.LoadDefaultConfig(context.TODO(),
        config.WithRegion("us-east-1"),
        config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
            "AKIAIOSFODNN7EXAMPLE",
            "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
            "",
        )),
    )
    if err != nil {
        t.Fatal(err)
    }

    client := sqs.NewFromConfig(cfg, func(o *sqs.Options) {
        o.BaseEndpoint = aws.String(endpoint)
    })

    // Use the client normally
    result, err := client.CreateQueue(context.TODO(), &sqs.CreateQueueInput{
        QueueName: aws.String("test-queue"),
    })
    if err != nil {
        t.Fatal(err)
    }
    t.Logf("Queue URL: %s", *result.QueueUrl)
}

API

NewServer

server := simfra.NewServer(services ...services.Service)

Creates a server with only the services you need. Each service constructor accepts optional dependencies (persistence DB, registry, etc.) - pass nil to use defaults.

WithDataDir

server := simfra.NewServer(sqssvc.New(nil, nil, nil)).WithDataDir("/tmp/simfra-test")

Enables SQLite persistence. Resources survive across Start/cleanup cycles if you reuse the same directory. Useful for testing restart behavior.

Start

endpoint, cleanup := server.Start()
defer cleanup()

Starts the server on a random available port. Returns the endpoint URL (e.g., http://127.0.0.1:54321) and a cleanup function that stops the server and closes the database.

StartOnPort

endpoint, cleanup := server.StartOnPort(4599)

Starts on a specific port. Panics if the port is already in use.

CreateAccount

account, err := server.CreateAccount("123456789012")

Creates an additional account programmatically. The default account (000000000000) is created automatically on startup with the standard test credentials.

DB

db := server.DB()

Returns the persistence database, or nil if persistence is not enabled.

AccountRegistry

registry := server.AccountRegistry()

Returns the account registry for advanced account management.

CAManager

ca := server.CAManager()

Returns the CA manager for TLS certificate operations.

Multiple Services

Register multiple services to test cross-service interactions:

import (
    snssvc "github.com/simfra-dev/simfra/internal/services/sns"
    sqssvc "github.com/simfra-dev/simfra/internal/services/sqs"
    iamsvc "github.com/simfra-dev/simfra/internal/services/iam"
)

func TestCrossService(t *testing.T) {
    server := simfra.NewServer(
        sqssvc.New(nil, nil, nil),
        snssvc.New(nil, nil),
        iamsvc.New(nil),
    )
    endpoint, cleanup := server.Start()
    defer cleanup()

    // SNS subscriptions to SQS queues work
    // IAM policy evaluation works
    // ...
}

Test Isolation

Each NewServer + Start call creates a completely isolated instance:

  • Separate in-memory stores
  • Separate account registry
  • Separate port
  • No shared state between tests

This means tests can run in parallel without interference:

func TestA(t *testing.T) {
    t.Parallel()
    endpoint, cleanup := simfra.NewServer(sqssvc.New(nil, nil, nil)).Start()
    defer cleanup()
    // ...
}

func TestB(t *testing.T) {
    t.Parallel()
    endpoint, cleanup := simfra.NewServer(sqssvc.New(nil, nil, nil)).Start()
    defer cleanup()
    // ...
}

When to Use the Embedded Server

Use the embedded server when:

  • Testing application logic that calls AWS APIs
  • Running unit/integration tests in CI without Docker
  • You need fast startup (milliseconds, not seconds)
  • You want test isolation without port management

Use the standalone process when:

  • You need Docker-backed services (CodeBuild, ECS, RDS, Lambda)
  • You are testing Terraform modules
  • You need persistence across test runs
  • You want to interact manually via the AWS CLI

Next Steps