README
¶
Test Patterns and Conventions
This document describes the testing patterns and conventions used in the neuwerk test suite.
Directory Structure
test/
├── helpers/ # Shared test utilities
│ ├── bdd.go # BaseBDDStage for BDD test stages
│ ├── client.go # HTTP/network client helpers
│ ├── config.go # Centralized test configuration
│ ├── debug.go # Debug and tracing utilities
│ ├── helpers.go # Common assertion helpers
│ ├── neuwerk.go # Neuwerk instance management
│ ├── setup.go # Network namespace setup
│ └── test_network.go # TestNetwork accessors
├── integration/ # Integration tests (require root)
│ ├── *_test.go # Test files
│ └── *_stage_test.go # BDD stage implementations
└── e2e/ # End-to-end tests
├── azure/ # Azure-specific tests
└── gcp/ # GCP-specific tests
Root Privilege Requirements
All integration tests require root privileges to create network namespaces and load BPF programs.
func TestSomething(t *testing.T) {
if os.Getuid() != 0 {
t.Skip("Integration tests require root privileges")
}
// ... test code
}
BDD Stage Pattern
Tests use a BDD (Behavior-Driven Development) stage pattern for readability:
func TestFeature(t *testing.T) {
given, when, then := NewMyStage(t)
given.
a_test_environment().and().
some_precondition()
when.
an_action_is_performed()
then.
the_expected_outcome_occurs()
}
Creating a New Stage
Stages should embed helpers.BaseBDDStage to reduce boilerplate:
type MyStage struct {
helpers.BaseBDDStage
// Stage-specific fields
testNet *helpers.TestNetwork
client *helpers.Client
}
func NewMyStage(t *testing.T) (*MyStage, *MyStage, *MyStage) {
s := &MyStage{
BaseBDDStage: helpers.NewBaseBDDStage(t),
}
return s, s, s // given, when, then all point to same instance
}
func (s *MyStage) and() *MyStage { return s }
Access fields via s.T, s.Require, s.Assert (capitalized).
Assert vs Require Conventions
Use require (stops on failure) for:
- Setup preconditions that must succeed
- Critical state that makes further testing meaningless
- Resource allocation/cleanup
// Setup must succeed - use require
instance, err := helpers.StartNeuwerk(t, testNet, opts)
s.Require.NoError(err, "failed to start neuwerk")
// Resource must exist before testing
s.Require.FileExists(certPath, "certificate must be generated")
Use assert (continues on failure) for:
- Non-critical assertions where you want to see all failures
- Verification of multiple independent conditions
- Soft checks that don't block further testing
// Check multiple properties - want to see all failures
s.Assert.Equal(expectedSerial, actualSerial, "serial should match")
s.Assert.True(cert.NotAfter.After(time.Now()), "cert should not be expired")
Test Naming Conventions
Test Functions
- Use descriptive names:
TestNetworkCRUD_CreateAndDelete - Group related tests with underscores:
TestAuth_TokenLogin,TestAuth_Logout
Stage Methods
- Use snake_case for readability:
a_test_environment_with_bootstrap() - Start with appropriate BDD keywords:
given:a_,an_,the_when: action verbs liketraffic_is_sent_,certificate_rotation_is_triggered_then:the_connection_succeeds(),all_certificates_are_valid()
Common Test Helpers
Network Setup
// Single node test environment
testNet := helpers.SetupSingleNodeWithCleanup(t)
// Multi-node HA environment
testNets := helpers.SetupMultiNodeWithCleanup(t, 3)
Starting Neuwerk
// Basic startup
instance, err := helpers.StartNeuwerk(t, testNet, helpers.NeuwerkOptions{})
// With client for API calls
instance, client := helpers.StartNeuwerkWithClient(t, testNet)
Waiting for Conditions
// Wait for health endpoint
helpers.WaitForHealthy(t, require, healthURL, "service should be healthy")
// Wait with custom timeout
helpers.WaitForHealthyWithTimeout(t, require, healthURL, 30*time.Second, "msg")
// Wait for arbitrary condition
helpers.WaitForCondition(t, require, func() bool {
return checkSomeState()
}, "condition should be met")
Connection Assertions
// Assert connection succeeds
helpers.AssertConnectionSucceeds(t, require, client, ip, port, "should connect")
// Assert connection is blocked
helpers.AssertConnectionBlocked(t, require, client, ip, port, "should be blocked")
// Assert with retry
helpers.AssertEventuallyConnects(t, require, client, ip, port, "should eventually connect")
Configuration Constants
Use constants from test/helpers/config.go for network addresses:
import "github.com/moolen/neuwerk/test/helpers"
// Instead of hardcoding:
// ip := net.ParseIP("10.100.1.10")
// Use:
ip := helpers.DefaultNeuwerkIngressIP
Running Tests
# Run all integration tests (requires root)
sudo go test ./test/integration/... -v
# Run specific test
sudo go test ./test/integration/... -run TestNetworkCRUD -v
# Run with race detection
sudo go test ./test/integration/... -race -v
# Run e2e tests (require cloud credentials)
sudo go test ./test/e2e/gcp/... -v
Click to show internal directories.
Click to hide internal directories.