The test suite uses scripts/test.sh as the unified test runner. It provides consistent behavior across different commands with support for gotestsum (recommended) or plain go test.
By default, the script auto-detects the available runner:
- If
RUNNERenv var is set, use that explicitly - If
gotestsumis installed, use it (with automatic retries for flaky tests) - Otherwise, fall back to
go test
To install gotestsum (recommended):
go install gotest.tools/gotestsum@latestTo force a specific runner:
RUNNER=go ./scripts/test.sh test # Force go test
RUNNER=gotestsum ./scripts/test.sh test # Force gotestsum# Run all tests (unit + integration)
./scripts/test.sh test
# Run unit tests only (uses -short flag)
./scripts/test.sh unit
# Run end-to-end tests
./scripts/test.sh e2e
# Run full suite with all backend combinations
./scripts/test.sh fullThe Makefile targets delegate to scripts/test.sh:
make test # ./scripts/test.sh test
make test/unit # ./scripts/test.sh unit
make test/e2e # ./scripts/test.sh e2e
make test/full # ./scripts/test.sh fullgo test ./... # All tests
go test ./... -short # Unit tests only
go test ./... -run "Integration" # Integration tests| Variable | Description | Default |
|---|---|---|
TEST |
Package(s) to test | ./internal/... |
RUN |
Filter tests by name pattern | (none) |
TESTARGS |
Additional arguments to pass to test command | (none) |
TESTINFRA |
Set to 1 to use persistent test infrastructure |
(none) |
TESTCOMPAT |
Set to 1 to run full backend compatibility suite |
(none) |
TESTREDISCLUSTER |
Set to 1 to enable Redis cluster tests |
(none) |
RUNNER |
Force test runner: gotestsum or go |
auto-detect |
# Test specific package
TEST='./internal/services/api' make test
# Run specific tests
RUN='TestJWT' make test
# Pass additional options
TESTARGS='-v' make test
# Combine options
RUN='TestListTenant' TEST='./internal/models' TESTINFRA=1 make test- Run test coverage
make test/coverage
# or with go test directly
go test ./... -coverprofile=coverage.out
# or to test specific package
TEST='./internal/services/api' make test/coverage- Visualize test coverage
Running the coverage test command above will generate the coverage.out file. You can visually inspect the test coverage with this command to see which statements are covered and more.
$ make test/coverage/html
# go tool cover -html=coverage.outBy default, the test suite runs only the primary backends (Miniredis + Dragonfly + Postgres) to keep feedback loops fast. To run the full suite including compatibility tests for alternative backends (Redis Stack, Redis Cluster), set the TESTCOMPAT environment variable:
# Run full test suite including all backend combinations
TESTCOMPAT=1 make testThis is useful for release testing or when making changes that might affect Redis compatibility.
Integration and e2e tests require external services like ClickHouse, LocalStack, RabbitMQ, etc. The test suite supports two modes for running these:
Persistent infrastructure (recommended): Run make up/test once, then use TESTINFRA=1 for all test runs. This is the recommended approach for local development.
Testcontainers (fallback): Without TESTINFRA=1, tests automatically spawn containers via Testcontainers. This is convenient for CI or one-off runs but adds startup overhead.
Redis and Dragonfly always use testcontainers (one container per test) since they start quickly. Heavier dependencies like LocalStack (AWS) or GCP emulators can take 15-30 seconds to initialize. With persistent infrastructure, you pay this cost once and get fast iteration from then on.
To run the test infrastructure:
$ make up/test
## to take the test infra down
# $ make down/testIt will run a Docker compose stack called outpost-test which runs the necessary services at ports ":30000 + port". For example, ClickHouse usually runs on port :9000, so in the test infra it will run on port :39000.
From here, you can provide env variable TESTINFRA=1 to tell the test suite to use these services instead of spawning testcontainers.
$ TESTINFRA=1 make testTip: You can $ export TESTINFRA=1 to use the test infra for the whole terminal session.
Here's a short template for how you can write integration tests that require an external test infra:
// Integration test should always start with "TestIntegration...() {}"
func TestIntegrationMyIntegrationTest(t *testing.T) {
t.Parallel()
// call testinfra.Start(t) to signal that you require the test infra.
// This helps the test runner properly terminate resources at the end.
t.Cleanup(testinfra.Start(t))
// use whichever infra you need
chConfig := testinfra.NewClickHouseConfig(t)
awsMQConfig := testinfra.NewMQAWSConfig(t, attributesMap)
rabbitmqConfig := testinfra.NewMQRabbitMQConfig(t)
// ...
}