check Style¶
The check style is the default and the most expressive. It combines a table-driven structure with closure-based check functions for composable, named assertions.
See The Check Function Pattern for a deep-dive into how and why this pattern works.
When to Use¶
- Methods on structs with dependencies.
- Constructors (
New). - Functions returning
errorwhere some cases expect errors and others do not. - Anything requiring different assertions per test case.
Generated Structure¶
// 1. Type alias — mirrors the function's return signature + *testing.T
type checkServiceCreateUserFn func(*testing.T, *userDomain.User, error)
// 2. Collector — type-safe variadic builder
var checkServiceCreateUser = func(fns ...checkServiceCreateUserFn) []checkServiceCreateUserFn {
return fns
}
// 3. Error check (generated when the function returns error)
func checkServiceCreateUserError(want string) checkServiceCreateUserFn {
return func(t *testing.T, _ *userDomain.User, err error) {
t.Helper()
if want == "" {
assert.NoErrorf(t, err, "checkServiceCreateUserError: expected no error, got %v", err)
return
}
if assert.Errorf(t, err, "checkServiceCreateUserError: expected error %q", want) {
assert.Containsf(t, err.Error(), want, "checkServiceCreateUserError mismatch")
}
}
}
// 4. Test function
func TestService_CreateUser(t *testing.T) {
tests := []struct {
name string
req *userDomain.UserCreateRequest
before func(*Service)
checks []checkServiceCreateUserFn
}{
{
name: "success case",
checks: checkServiceCreateUser(
checkServiceCreateUserError(""),
),
},
{
name: "fail case",
checks: checkServiceCreateUser(
checkServiceCreateUserError("expected error message"),
),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
s := New(nil)
if tt.before != nil {
tt.before(s)
}
r, err := s.CreateUser(context.Background(), tt.req)
for _, c := range tt.checks {
c(t, r, err)
}
})
}
}
What Is Generated¶
| Element | Always? | Description |
|---|---|---|
checkXxxFn type |
Yes | Signature: *testing.T + function's return types |
checkXxx collector |
Yes | Variadic identity — builds the checks slice |
checkXxxError |
When HasError == true |
Checks error presence and message content |
before field |
When IsMethod == true |
Per-case mock setup / state mutation |
| Context injection | When first param is context.Context |
Injected as context.Background(), not in table |
| TODO cases | Configurable | Placeholder rows controlled by number_of_todos |
Adding Your Own Check Functions¶
The generated checkXxxError is a starting point. Add domain-specific checks by hand:
func checkUserName(want string) checkServiceCreateUserFn {
return func(t *testing.T, u *userDomain.User, _ error) {
t.Helper()
assert.Equalf(t, want, u.Name, "checkUserName: got %q, want %q", u.Name, want)
}
}
func checkUserIDNotEmpty(t *testing.T, u *userDomain.User, _ error) {
t.Helper()
assert.NotEmptyf(t, u.ID, "checkUserIDNotEmpty: ID should not be empty")
}
Then compose them in the table:
{
name: "creates user with correct fields",
req: &userDomain.UserCreateRequest{Name: "alice"},
before: func(s *Service) {
s.repo.(*mockUserRepository).
On("CreateUser", mock.Anything, mock.Anything).
Return(&userDomain.User{ID: "123", Name: "alice"}, nil)
},
checks: checkServiceCreateUser(
checkServiceCreateUserError(""),
checkUserName("alice"),
checkUserIDNotEmpty,
),
},