176 lines
4.7 KiB
Markdown
176 lines
4.7 KiB
Markdown
# NATS In-Memory Test Server
|
|
|
|
## When to Use This Example
|
|
|
|
Use this when:
|
|
- Testing message queue integrations with NATS
|
|
- Need pub/sub functionality in tests
|
|
- Want fast, in-memory NATS server (no Docker, no binary)
|
|
- Testing event-driven architectures
|
|
|
|
**Dependency Level**: Level 1 (In-Memory) - Pure Go, official test harness
|
|
|
|
## Implementation
|
|
|
|
### Setup Test Infrastructure
|
|
|
|
Many official SDKs provide test harnesses. Here's NATS:
|
|
|
|
```go
|
|
// internal/testutils/nats.go
|
|
package testutils
|
|
|
|
import (
|
|
nserver "github.com/nats-io/nats-server/v2/server"
|
|
natsserver "github.com/nats-io/nats-server/v2/test"
|
|
"github.com/projectdiscovery/freeport"
|
|
)
|
|
|
|
// RunNATsServer runs a NATS server in-memory for testing.
|
|
// Uses the official NATS SDK test harness - no binary download needed!
|
|
func RunNATsServer() (*nserver.Server, error) {
|
|
opts := natsserver.DefaultTestOptions
|
|
|
|
// Allocate free port to prevent conflicts in parallel tests
|
|
tcpPort, err := freeport.GetFreePort("127.0.0.1", freeport.TCP)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
opts.Port = tcpPort.Port
|
|
|
|
// Start NATS server in-memory (pure Go!)
|
|
return natsserver.RunServer(&opts), nil
|
|
}
|
|
|
|
// RunNATsServerWithJetStream runs NATS with JetStream enabled
|
|
func RunNATsServerWithJetStream() (*nserver.Server, error) {
|
|
opts := natsserver.DefaultTestOptions
|
|
|
|
tcpPort, err := freeport.GetFreePort("127.0.0.1", freeport.TCP)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
opts.Port = tcpPort.Port
|
|
opts.JetStream = true
|
|
|
|
return natsserver.RunServer(&opts), nil
|
|
}
|
|
```
|
|
|
|
## Usage in Integration Tests
|
|
|
|
### Basic Pub/Sub Test
|
|
|
|
```go
|
|
//go:build integration
|
|
|
|
package integration_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
"github.com/nats-io/nats.go"
|
|
"github.com/stretchr/testify/require"
|
|
"myproject/internal/testutils"
|
|
)
|
|
|
|
func TestNATSPubSub_Integration(t *testing.T) {
|
|
// Start NATS server in-memory (Level 1 - pure Go!)
|
|
natsServer, err := testutils.RunNATsServer()
|
|
require.NoError(t, err)
|
|
defer natsServer.Shutdown()
|
|
|
|
// Connect to in-memory NATS
|
|
natsAddress := "nats://" + natsServer.Addr().String()
|
|
nc, err := nats.Connect(natsAddress)
|
|
require.NoError(t, err)
|
|
defer nc.Close()
|
|
|
|
// Test pub/sub
|
|
received := make(chan string, 1)
|
|
_, err = nc.Subscribe("test.subject", func(msg *nats.Msg) {
|
|
received <- string(msg.Data)
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Publish message
|
|
err = nc.Publish("test.subject", []byte("hello"))
|
|
require.NoError(t, err)
|
|
|
|
// Wait for message
|
|
select {
|
|
case msg := <-received:
|
|
require.Equal(t, "hello", msg)
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatal("timeout waiting for message")
|
|
}
|
|
}
|
|
```
|
|
|
|
### Real-World Usage Example (gRPC + NATS)
|
|
|
|
```go
|
|
// tests/gointegration/remote_traces_test.go
|
|
type RemoteTracesTestSuite struct {
|
|
suite.Suite
|
|
natsServer *nserver.Server
|
|
natsAddress string
|
|
nc *nats.Conn
|
|
// ... other fields
|
|
}
|
|
|
|
func (suite *RemoteTracesTestSuite) SetupSuite() {
|
|
// Start NATS server in-memory
|
|
natsServer, err := testutils.RunNATsServer()
|
|
suite.Require().NoError(err)
|
|
|
|
suite.natsServer = natsServer
|
|
suite.natsAddress = "nats://" + natsServer.Addr().String()
|
|
|
|
// Connect application to in-memory NATS
|
|
suite.nc, err = natsremotetraces.ConnectToRemoteTracesSession(
|
|
suite.ctx, suite.natsAddress, numWorkers, numWorkers, channelSize)
|
|
suite.Require().NoError(err)
|
|
|
|
// Start gRPC server with NATS backend
|
|
// ... rest of setup
|
|
}
|
|
|
|
func (suite *RemoteTracesTestSuite) TearDownSuite() {
|
|
suite.nc.Close()
|
|
suite.natsServer.Shutdown() // Clean shutdown
|
|
}
|
|
|
|
func (suite *RemoteTracesTestSuite) TestMessageFlow() {
|
|
// Test your application logic that uses NATS
|
|
// ...
|
|
}
|
|
```
|
|
|
|
## Why This is Excellent
|
|
|
|
- **Pure Go** - NATS server imported as library (no binary download)
|
|
- **Official** - Uses NATS SDK's official test harness
|
|
- **Fast** - Starts in microseconds
|
|
- **Reliable** - Same behavior as production NATS
|
|
- **Portable** - Works anywhere Go runs
|
|
- **No Docker** - No external dependencies
|
|
- **Parallel-Safe** - Free port allocation prevents conflicts
|
|
|
|
## Other Libraries with Test Harnesses
|
|
|
|
- **Redis**: `github.com/alicebob/miniredis` - Pure Go in-memory Redis
|
|
- **NATS**: `github.com/nats-io/nats-server/v2/test` (shown above)
|
|
- **PostgreSQL**: `github.com/jackc/pgx/v5/pgxpool` with pgx mock
|
|
- **MongoDB**: `github.com/tryvium-travels/memongo` - In-memory MongoDB
|
|
|
|
## Key Takeaways
|
|
|
|
1. **Check for official test harnesses first** - Many popular libraries provide them
|
|
2. **Use free port allocation** - Prevents conflicts in parallel tests
|
|
3. **Clean shutdown** - Always call `Shutdown()` in teardown
|
|
4. **Reusable infrastructure** - Same setup for unit, integration, and system tests
|