Testing
Development follows test-driven development:
- Write a failing test
- Implement minimal code to pass
- Refactor
- Commit
Tests run in two modes, controlled by build tags:
- Unit tests (default) — use mock executors, no Docker required
- Integration tests (
-tags integration) — run against real Docker
Each package that needs both modes has two files:
mode_unit_test.go // build tag: !integration
mode_integration_test.go // build tag: integration
The cmdexec.MockExecutor (from pkg/cmdexec) replaces real CLI calls in unit tests. It supports two dispatch modes:
Queue results in order with AddResult():
mock := &cmdexec.MockExecutor{}
mock.AddResult(`{"Id":"abc123"}`, "", nil) // first call returns this
mock.AddResult("", "", nil) // second call returns this
client := docker.NewClient(mock)
Use OnCall for concurrent tests or when result dispatch depends on the command:
mock := &cmdexec.MockExecutor{
OnCall: func(args []string, stdin string) cmdexec.MockResult {
if args[0] == "inspect" {
return cmdexec.MockResult{Stdout: `[{"Id":"abc"}]`}
}
return cmdexec.MockResult{}
},
}
All calls are recorded and can be inspected:
assert.Equal(t, "create", mock.Calls[0].Args[0])
assert.Contains(t, mock.Calls[0].Args, "--name")
For methods like ContainerExists() that check exit codes, a plain fmt.Errorf won’t work. You need a real *exec.ExitError with exit code 1:
mock.AddResult("", "", &exec.ExitError{ProcessState: exitCode1(t)})
The exitCode1(t) helper runs sh -c "exit 1" to obtain a real *os.ProcessState. This helper is defined per test package (not shared).
The cmdexec.RecordingExecutor wraps a real executor and records all calls with their results. Useful for observing actual CLI I/O during integration tests:
rec := &cmdexec.RecordingExecutor{Inner: &cmdexec.OSExecutor{}}
client := docker.NewClient(rec)
// ... run operations ...
// Dump all recorded calls
t.Log(rec.Dump())
Integration tests use unique realms to avoid resource conflicts when running in parallel. Each test package generates a random realm prefix:
realm := fmt.Sprintf("test-%d-%d", os.Getpid(), rand.Int())
This ensures tests can run concurrently without interfering with each other or with the user’s sind clusters.
Tests use testify:
assert— for non-fatal assertions (test continues)require— for fatal assertions (test stops immediately)
assert.Equal(t, "running", info.Status)
require.NoError(t, err)