Check Types and Checks¶
Experimental
gen-cases and .testspec.yaml are experimental — an exploration of the approach. Schema, API, and behavior may change. Feedback welcome.
Alternatively, install the AI agent skills and let an AI generate cases directly from source code — no spec file required.
These two sections form the assertion vocabulary of a spec. check_types declares the function types used for assertions; checks catalogues the individual assertion functions.
check_types¶
A check type is a Go function type alias that defines the shape of a single assertion closure. It also names the composer — the variadic helper that collects assertions per test case.
check_types:
- id: <identifier>
type_name: <Go type name>
signature: <Go func signature>
composer: <composer function name>
package: <import path — omit if same package>
description: <what this type validates>
Fields¶
| Field | Required | Description |
|---|---|---|
id |
yes | Internal identifier used to link checks to this type via for_type. |
type_name |
yes | Go type name (e.g. engineTestCheckFn, TestCheckResultFn). |
signature |
yes | Full Go function signature (e.g. func(*testing.T, *Engine)). |
composer |
yes | Name of the variadic collector function (e.g. checkEngine, CheckResult). |
package |
no | Import path if the type is defined in another package. Omit for same-package types. |
description |
yes | What the type asserts — shown in generated // ai-hint: comments. |
Same-package check type¶
check_types:
- id: engine_check
type_name: engineTestCheckFn
signature: "func(*testing.T, *Engine)"
composer: checkEngine
description: Validates the Engine state after Start()/Stop().
Corresponds to:
type engineTestCheckFn func(*testing.T, *Engine)
var checkEngine = func(fns ...engineTestCheckFn) []engineTestCheckFn { return fns }
Cross-package check type¶
check_types:
- id: result_check
type_name: TestCheckResultFn
signature: "func(*testing.T, model.Notifier, *model.Result)"
composer: CheckResult
package: github.com/padiazg/notifier/model
description: Validates the *model.Result returned by Deliver.
When package is set, gen-cases qualifies both the composer call and the check function calls with the package's last path segment:
Multiple check types in one test¶
A single test can have multiple check types — e.g. one for the engine state and one for notifications:
check_types:
- id: notification_check
type_name: notificationCheckFn
signature: "func(*testing.T, *Engine, *model.Notification)"
composer: checkNotifications
description: Validates the engine state and the notification after dispatch.
- id: error_check
type_name: errorCheckFn
signature: "func(*testing.T, error)"
composer: checkError
description: Validates the error returned by Dispatch.
checks¶
The checks catalogue lists each assertion function available in this test. Each entry is linked to a check type via for_type.
checks:
- id: <function name>
for_type: <check_type id>
scope: global | local
signature: <full signature with return type>
when: <when to use this check>
params:
- name: <param name>
type: <Go type>
doc: <what this param controls>
sentinel_empty: <meaning of zero/empty value, if any>
captures: [<var1>, <var2>]
Fields¶
| Field | Required | Description |
|---|---|---|
id |
yes | The Go function name. Also the prefix used in case checks entries. |
for_type |
yes | The check_type.id this function belongs to. |
scope |
yes | global or local — see Scope below. |
signature |
yes | Full Go signature including return type (e.g. hasErrors(has bool) engineTestCheckFn). |
when |
yes | Description of when to use this check and what invariant it verifies. |
params |
no | Documentation for each parameter — used in // ai-hint: comments. |
captures |
no | Variables from the outer closure this check reads (relevant for scope: local). |
Scope¶
scope: global — the function is defined at package scope (in common_test.go, the model package, or at the top of the test file). It does not close over test-local variables.
- id: hasErrors
for_type: engine_check
scope: global
signature: "hasErrors(has bool) engineTestCheckFn"
when: >
Verify whether the package-level `errors` variable has entries.
Use has=true when a notifier fails to Connect().
Use has=false when all notifiers connect correctly.
Corresponding Go:
func hasErrors(has bool) engineTestCheckFn {
return func(t *testing.T, e *Engine) {
t.Helper()
if has {
assert.NotEmpty(t, errors, "hasErrors: expected at least one error")
} else {
assert.Empty(t, errors, "hasErrors: expected no errors")
}
}
}
scope: local — the function is defined inside TestXxx, before the tests slice. It closes over variables from shared_setup (e.g. a logger buffer, a captured value).
- id: checkClientType
for_type: notifier_check
scope: local
signature: "checkClientType(clientType interface{}) model.TestCheckNotifierFn"
when: >
Validate that n.client is of the expected type.
Compare with reflect.TypeOf(clientType) vs reflect.TypeOf(n.client).
captures: []
Corresponding Go (defined inside TestWebhookNotifier_getClient):
checkClientType := func(clientType interface{}) model.TestCheckNotifierFn {
return func(t *testing.T, n model.Notifier) {
t.Helper()
// ai-hint: implement assertion
// compare reflect.TypeOf(clientType) vs reflect.TypeOf(n.(*WebhookNotifier).client)
}
}
params fields¶
| Field | Required | Description |
|---|---|---|
name |
yes | Parameter name as it appears in the function signature. |
type |
yes | Go type. |
doc |
yes | What this parameter controls. |
sentinel_empty |
no | The special meaning of the zero/empty value. Common pattern: empty string means "no error expected". |
Sentinel empty pattern¶
checks:
- id: CheckResultError
for_type: result_check
scope: global
signature: "model.CheckResultError(want string) model.TestCheckResultFn"
when: >
Use in all Deliver cases to validate result.Error.
want="" validates success (result.Error == nil).
want="substring" validates that result.Error.Error() contains that substring.
params:
- name: want
type: string
doc: Expected error substring.
sentinel_empty: "empty string asserts result.Error is nil (success)"
Corresponding check implementation:
func CheckResultError(want string) TestCheckResultFn {
return func(t *testing.T, n Notifier, r *Result) {
t.Helper()
if want == "" {
assert.NoErrorf(t, r.Error, "CheckResultError: expected no error, got %v", r.Error)
return
}
if assert.Errorf(t, r.Error, "CheckResultError: expected error %q", want) {
assert.Containsf(t, r.Error.Error(), want, "CheckResultError mismatch")
}
}
}
captures¶
Lists variables from the outer test scope that this local check closes over. This is documentation for the AI — it helps the AI understand what variables are already in scope when implementing the check.
- id: checkLogContains
for_type: service_check
scope: local
signature: "checkLogContains(want string) serviceCheckFn"
captures: [buf]
Means the check reads buf (e.g. a bytes.Buffer declared in shared_setup):