Skip to content

Commit 566fa13

Browse files
committed
feat(testing): add AssertMustFoo() variants that call t.FailNow() on failure
Add 14 new AssertMustFoo() functions that automatically call t.FailNow() when assertions fail, providing a cleaner alternative to the manual `if !AssertFoo() { t.FailNow() }` pattern. New functions follow Go testing conventions where "Fatal" methods terminate execution, similar to t.Error() vs t.Fatal(): - AssertMustEqual[T], AssertMustNotEqual[T], AssertMustSliceEqual[T] - AssertMustTrue, AssertMustFalse, AssertMustNil, AssertMustNotNil - AssertMustContains, AssertMustError, AssertMustNoError, AssertMustErrorIs - AssertMustPanic, AssertMustNoPanic, AssertMustTypeIs[T] All functions include comprehensive tests using MockT.Run() to verify both success (execution continues) and failure (t.FailNow() called, execution terminates) scenarios. Documentation added to README.md with clear explanation of Error vs Fatal patterns and usage examples. Signed-off-by: Alejandro Mery <amery@apptly.co>
1 parent 01346f9 commit 566fa13

File tree

5 files changed

+677
-40
lines changed

5 files changed

+677
-40
lines changed

README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,57 @@ both `*testing.T` and `MockT`:
507507
* `AssertPanic(t, fn, expectedPanic, msg...)` / `AssertNoPanic(t, fn, msg...)` -
508508
panic testing.
509509

510+
#### Fatal Assertions
511+
512+
All `AssertMustFoo()` functions call the corresponding `AssertFoo()` function
513+
and automatically call `t.FailNow()` if the assertion fails, terminating test
514+
execution immediately. These follow Go testing conventions where "Fatal"
515+
methods terminate execution, similar to `t.Error()` vs `t.Fatal()`.
516+
517+
**Key Differences:**
518+
519+
* `AssertFoo()` - like `t.Error()`, logs failure and allows test to continue.
520+
* `AssertMustFoo()` - like `t.Fatal()`, logs failure and terminates test
521+
execution.
522+
523+
**Fatal Assertion Functions:**
524+
525+
* `AssertMustEqual[T](t, expected, actual, msg...)` - terminate on inequality.
526+
* `AssertMustNotEqual[T](t, expected, actual, msg...)` - terminate on equality.
527+
* `AssertMustSliceEqual[T](t, expected, actual, msg...)` - terminate on slice
528+
inequality.
529+
* `AssertMustTrue(t, condition, msg...)` /
530+
`AssertMustFalse(t, condition, msg...)` - terminate on boolean mismatch.
531+
* `AssertMustNil(t, value, msg...)` / `AssertMustNotNil(t, value, msg...)` -
532+
terminate on nil check failure.
533+
* `AssertMustContains(t, text, substring, msg...)` - terminate if substring not
534+
found.
535+
* `AssertMustError(t, err, msg...)` / `AssertMustNoError(t, err, msg...)` -
536+
terminate on error expectation mismatch.
537+
* `AssertMustErrorIs(t, err, target, msg...)` - terminate on error chain
538+
mismatch.
539+
* `AssertMustTypeIs[T](t, value, msg...) T` - terminate on type assertion
540+
failure, returns cast value.
541+
* `AssertMustPanic(t, fn, expectedPanic, msg...)` /
542+
`AssertMustNoPanic(t, fn, msg...)` - terminate on panic expectation mismatch.
543+
544+
**Usage Examples:**
545+
546+
```go
547+
// Error pattern - logs failure, test continues
548+
AssertEqual(t, expected, actual, "value check")
549+
// execution continues even if assertion fails
550+
551+
// Fatal pattern - logs failure, test terminates
552+
AssertMustEqual(t, expected, actual, "critical value check")
553+
// execution stops here if assertion fails
554+
555+
// Traditional early abort pattern (equivalent to AssertMust*)
556+
if !AssertEqual(t, expected, actual, "critical value") {
557+
t.FailNow()
558+
}
559+
```
560+
510561
### Advanced Testing Utilities
511562

512563
* `TestCase` interface - standardised interface for table-driven tests with
@@ -563,7 +614,7 @@ Context-aware error group with cancellation:
563614
For detailed development setup, build commands, and AI agent guidance:
564615

565616
* [AGENT.md](./AGENT.md) - Development guidelines, build system, and testing
566-
patterns
617+
patterns.
567618

568619
### Quick Start
569620

TESTING.md

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,27 @@ core.AssertPanic(t, func() { panic("test") }, "panic")
112112
core.AssertNoPanic(t, func() { /* safe code */ }, "no panic")
113113
```
114114

115+
**Fatal Assertions (`AssertMust*`):**
116+
117+
All `AssertMustFoo()` functions call the corresponding `AssertFoo()` function
118+
and automatically call `t.FailNow()` if the assertion fails, terminating test
119+
execution immediately:
120+
121+
```go
122+
// Standard assertions - log failure, test continues
123+
core.AssertEqual(t, expected, actual, "value")
124+
// ... test execution continues even if assertion fails
125+
126+
// Fatal assertions - log failure, test terminates
127+
core.AssertMustEqual(t, expected, actual, "critical value")
128+
// ... test execution stops here if assertion fails
129+
```
130+
131+
**Key Difference:**
132+
133+
* `AssertFoo()` - like `t.Error()`, allows test to continue after failure.
134+
* `AssertMustFoo()` - like `t.Fatal()`, terminates test on failure.
135+
115136
### Assertion Function Hierarchy Guidelines
116137

117138
When creating custom assertion functions, follow these hierarchy principles.
@@ -151,11 +172,11 @@ func AssertCustomTrue(t core.T, condition bool, name string,
151172

152173
**Key Principles:**
153174

154-
- **Avoid circular dependencies**: Don't test assertions using themselves.
155-
- **Maintain consistency**: Derived functions should use base functions for
175+
* **Avoid circular dependencies**: Don't test assertions using themselves.
176+
* **Maintain consistency**: Derived functions should use base functions for
156177
uniform error formatting and logging behaviour.
157-
- **Use helper pattern**: Always call `t.Helper()` in assertion functions.
158-
- **Follow naming**: Use `Assert` prefix and descriptive suffixes.
178+
* **Use helper pattern**: Always call `t.Helper()` in assertion functions.
179+
* **Follow naming**: Use `Assert` prefix and descriptive suffixes.
159180

160181
**Understanding Assertion Prefixes:**
161182

@@ -178,10 +199,10 @@ core.AssertTrue(t, SliceContains(got, k), "key %q present", k)
178199

179200
**Prefix Guidelines:**
180201

181-
- Use **short, descriptive prefixes** (1-3 words).
182-
- The prefix will be combined with the actual value: `"prefix: value"`.
183-
- For formatted messages, use printf-style formatting: `"contains %v", key`.
184-
- Avoid complete sentences - they become redundant with the logged value.
202+
* Use **short, descriptive prefixes** (1-3 words).
203+
* The prefix will be combined with the actual value: `"prefix: value"`.
204+
* For formatted messages, use printf-style formatting: `"contains %v", key`.
205+
* Avoid complete sentences - they become redundant with the logged value.
185206

186207
## COMPLIANT Test Structure Patterns
187208

@@ -916,11 +937,11 @@ Run with: `go test -tags=integration`
916937

917938
### Test Naming
918939

919-
- Test functions: `TestFunctionName`.
920-
- Test types: `functionNameTestCase`.
921-
- TestCase interface methods:
922-
- `func (tc testCaseType) Name() string` - returns test case name
923-
- `func (tc testCaseType) Test(t *testing.T)` - runs the test logic
940+
* Test functions: `TestFunctionName`.
941+
* Test types: `functionNameTestCase`.
942+
* TestCase interface methods:
943+
* `func (tc testCaseType) Name() string` - returns test case name
944+
* `func (tc testCaseType) Test(t *testing.T)` - runs the test logic
924945

925946
### Documentation
926947

@@ -943,11 +964,11 @@ func (tc parseURLTestCase) Test(t *testing.T) {
943964

944965
### Clean Tests
945966

946-
- Use `t.Helper()` in all helper functions.
947-
- Prefer table-driven tests over individual test functions.
948-
- Keep setup and clean-up minimal.
949-
- Use meaningful assertion descriptions.
950-
- Test both success and failure paths.
967+
* Use `t.Helper()` in all helper functions.
968+
* Prefer table-driven tests over individual test functions.
969+
* Keep setup and clean-up minimal.
970+
* Use meaningful assertion descriptions.
971+
* Test both success and failure paths.
951972

952973
### Test Data
953974

@@ -1101,28 +1122,28 @@ func validationTestCases(fieldName string) []validationTestCase {
11011122

11021123
Before committing test code, verify ALL 6 requirements:
11031124

1104-
- [ ] **TestCase Interface Validations**: Added `var _ TestCase = ...` for
1125+
* [ ] **TestCase Interface Validations**: Added `var _ TestCase = ...` for
11051126
all test case types
1106-
- [ ] **Factory Functions**: Created `newTestCaseTypeName()` for all test
1127+
* [ ] **Factory Functions**: Created `newTestCaseTypeName()` for all test
11071128
case types
1108-
- [ ] **Factory Usage**: All test case declarations use factory functions
1129+
* [ ] **Factory Usage**: All test case declarations use factory functions
11091130
(no naked struct literals)
1110-
- [ ] **RunTestCases Usage**: All test functions use `RunTestCases(t, cases)`
1111-
- [ ] **Anonymous Functions**: No `t.Run()` anonymous functions longer than
1131+
* [ ] **RunTestCases Usage**: All test functions use `RunTestCases(t, cases)`
1132+
* [ ] **Anonymous Functions**: No `t.Run()` anonymous functions longer than
11121133
3 lines
1113-
- [ ] **Test Case List Factories**: Complex test case generation uses
1134+
* [ ] **Test Case List Factories**: Complex test case generation uses
11141135
`myFooTestCases()` factory functions
11151136

11161137
## Summary
11171138

11181139
By following these **MANDATORY** guidelines, all darvaza.org projects will
11191140
have:
11201141

1121-
- **Consistent testing patterns** across the entire ecosystem.
1122-
- **Lint-compliant code** that meets complexity requirements.
1123-
- **Maintainable tests** that are easy to read and modify.
1124-
- **Reliable test suites** with excellent error reporting.
1125-
- **Comprehensive coverage** with meaningful assertions.
1142+
* **Consistent testing patterns** across the entire ecosystem.
1143+
* **Lint-compliant code** that meets complexity requirements.
1144+
* **Maintainable tests** that are easy to read and modify.
1145+
* **Reliable test suites** with excellent error reporting.
1146+
* **Comprehensive coverage** with meaningful assertions.
11261147

11271148
The key is to treat test code with the same care as production code, using
11281149
the excellent utilities provided by `darvaza.org/core` to maintain

TESTING_core.md

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,21 +158,20 @@ func TestAssertEqual(t *testing.T) {
158158

159159
#### Testing Fatal/FailNow Scenarios
160160

161-
MockT's Run() method enables testing functions that call Fatal/FailNow:
161+
MockT's Run() method enables testing functions that call Fatal/FailNow methods,
162+
including the `AssertMust*` family of functions which automatically call
163+
`t.FailNow()` on assertion failure:
162164

163165
```go
164166
func TestFatalAssertion(t *testing.T) {
165167
mock := &MockT{}
166168

167-
// Test function that would call Fatal on failure
168-
ok := mock.Run("fatal test", func(mt T) {
169-
// This calls mt.Error() but doesn't panic
170-
AssertEqual(mt, 1, 2, "will fail")
171-
172-
// To test actual Fatal behaviour:
173-
if !AssertEqual(mt, 1, 2, "critical failure") {
174-
mt.FailNow() // This will panic and be caught by Run()
175-
}
169+
// Test AssertMust* functions that call FailNow() automatically
170+
ok := mock.Run("fatal assertion test", func(mt T) {
171+
// This will call mt.FailNow() and cause Run() to return false
172+
AssertMustEqual(mt, 1, 2, "critical failure")
173+
// Execution stops here - this line won't be reached
174+
mt.Log("should not reach here")
176175
})
177176

178177
// Verify the test failed and was handled properly
@@ -185,6 +184,18 @@ func TestFatalAssertion(t *testing.T) {
185184
if !mock.HasErrors() {
186185
t.Error("Should have recorded error messages")
187186
}
187+
188+
// Traditional pattern - equivalent to AssertMust*
189+
mock.Reset()
190+
ok = mock.Run("manual fatal test", func(mt T) {
191+
if !AssertEqual(mt, 1, 2, "manual check") {
192+
mt.FailNow() // This will panic and be caught by Run()
193+
}
194+
})
195+
// Verify same behaviour as AssertMust*
196+
if ok {
197+
t.Error("Manual FailNow should also cause test failure")
198+
}
188199
}
189200
```
190201

0 commit comments

Comments
 (0)