Chapter 07: Check Factory Closures¶
Description¶
A check factory is a function that takes expected values as parameters and returns a check closure: checkValue(want float64) checkConvertFn { return func(t *testing.T, got Temperature, err error) { assert.Equal(t, want, got.Value) } }. The closure captures the expected value at test-definition time and asserts against the actual result at test-execution time.
Real-world examples:
pantry/internal/core/domain/product_test.go:93-103—checkApplyMovementError(want string)go-crap/internal/report/table_test.go:21-26—checkOutputContains(want string)go-crap/internal/report/json_test.go:100-111—checkReportSchema,checkReportVersionnotifier/connector/webhook/webhook_test.go:28-43—checkName(name string)
Code¶
type Temperature struct {
Value float64
Unit TemperatureUnit
}
func Convert(t Temperature, to TemperatureUnit) (Temperature, error) {
// converts between Celsius, Fahrenheit, Kelvin
}
Test¶
type checkConvertFn func(*testing.T, Temperature, error)
var checkConvert = func(fns ...checkConvertFn) []checkConvertFn { return fns }
func TestConvert(t *testing.T) {
// Factory: takes expected value, returns check closure
checkValue := func(want float64) checkConvertFn {
return func(t *testing.T, got Temperature, err error) {
t.Helper()
assert.NoError(t, err)
assert.InDelta(t, want, got.Value, 0.01)
}
}
checkUnit := func(want TemperatureUnit) checkConvertFn {
return func(t *testing.T, got Temperature, err error) {
t.Helper()
assert.NoError(t, err)
assert.Equal(t, want, got.Unit)
}
}
checkError := func(want string) checkConvertFn {
return func(t *testing.T, _ Temperature, err error) {
t.Helper()
if assert.Error(t, err) {
assert.Contains(t, err.Error(), want)
}
}
}
tests := []struct {
name string
input Temperature
target TemperatureUnit
checks []checkConvertFn
}{
{
name: "Celsius to Fahrenheit",
input: Temperature{Value: 100, Unit: Celsius},
target: Fahrenheit,
checks: checkConvert(
checkValue(212), // closure captures 212
checkUnit(Fahrenheit), // closure captures Fahrenheit
),
},
// ...
}
}
Testing Approach¶
Check factory closures are the core of the closure-check pattern:
- Closure captures expected — each call to
checkValue(212)creates a closure wherewantis bound to212. The closure is stored in thechecksslice and executed later with the actual result. This is the fundamental Go pattern for deferred assertion logic. - Parameterized reuse —
checkValue,checkUnit,checkErrorare defined once and reused across all test cases. A 9-case test table with 3 assertions each = 27 assertions, but only 3 factory functions. assert.InDeltafor floats — the temperature converter usesassert.InDelta(t, want, got.Value, 0.01)instead ofassert.Equalto handle floating-point precision issues across conversions.- Composability — factories compose naturally:
checkValue + checkUnitfor success cases,checkErroralone for failure cases. The presence or absence of certain checks in a test case documents what that case exercises.
View source code on GitHub