Testing modules
Module testing has three layers, each catching what the previous can’t.
1. Decode tests (fast, always on)
Section titled “1. Decode tests (fast, always on)”Pure functions over your DecodeConfig — every go test run, no backend:
func TestDecodeConfig(t *testing.T) { spec, err := decode(t, `maxmemory = "256mb"`) // parse HCL, call DecodeConfig … // strictness is a feature — prove it stays: if _, err := decode(t, `maxmemoryy = "1"`); err == nil { t.Fatal("unknown attributes must error") }}Cover: defaults, validation errors (they’re UX — assert the messages),
version gates (RequireVersion firing on the right majors and passing on
empty), and the Describe drift guard.
2. enginetest: your driver against a real backend
Section titled “2. enginetest: your driver against a real backend”doze-sdk/enginetest boots a real engine straight from your driver —
resolve → provision → run your SpawnPlan to readiness → converge — with no
doze daemon or proxy in the way. It’s doze’s analog of Terraform’s acceptance
harness:
//go:build acceptance
func TestRolesConverge(t *testing.T) { b := enginetest.Boot(t, postgres.Driver{}, enginetest.Options{ Version: "18", HCL: `postgres "acc" { role "app" { password = "x" login = true } }`, }) // b is a live backend: connect to b.Port(), assert the role exists, // then change config and b.Converge() again to test idempotency.}Two deliberate properties:
- It skips without a binary.
BootrequiresDOZE_<ENGINE>_BINDIRand callst.Skipwhen unset — plaingo teststays green offline. Gate the files with anacceptancebuild tag. - It tests convergence, not just decode — that
role/extension/bucketblocks actually materialize in the running engine, including the second run doing nothing (idempotency is the contract).
3. Acceptance in CI
Section titled “3. Acceptance in CI”The official modules run the acceptance matrix weekly and on demand: CI builds
each engine’s real backend from the
doze-binaries recipes, exports the bindir, and
runs go test -tags acceptance. For your module, the same shape works with
any way of producing a backend binary — build from source, download upstream,
or point at your mirror:
- run: | # produce a backend, then: export DOZE_MYENGINE_BINDIR=$PWD/backend/bin go test -tags acceptance -timeout 20m ./...The end-to-end smoke that isn’t a test
Section titled “The end-to-end smoke that isn’t a test”Before releasing, run the real thing once — it catches the integration surprises unit layers can’t:
go build -o /tmp/my-plugin ./pluginDOZE_MYTYPE_PLUGIN=/tmp/my-plugin doze up # in a scratch projectdoze lint && doze status && <connect a real client>Ten seconds, and it exercises the actual plugin protocol, the proxy splice, readiness, and reaping — the full production path minus the registry.