Skip to content

Commit 66e121a

Browse files
authored
feat: add *T# assertions fuctions for use from unit tests. (#12)
1 parent a242429 commit 66e121a

File tree

4 files changed

+92
-4
lines changed

4 files changed

+92
-4
lines changed

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
When you don't need error handling
44

5-
I found myself using this snipped lots.
5+
I found myself using this snipped in a few places.
66

77
``` go
88
func must[T any](out T, err error) T {
@@ -28,7 +28,7 @@ Occasionally a little more, so made a home for them, DRY.
2828
``` go
2929
// NoError panics on error.
3030
//
31-
// Use this because test coverage.
31+
// You can use this instead of ignoring errors that never happen by contract.
3232
func NoError(err error) {
3333
if err != nil {
3434
panic(err)
@@ -69,12 +69,22 @@ func ExampleB3() {
6969

7070
// output:
7171
// a
72-
}
72+
}
7373
```
7474

7575
Assertions have optional debug arguments, to provide additional information when
7676
violated. Usually, just pass in the line comment as string.
7777

78+
### Usage in unit tests
79+
80+
`must.*T#` will cause the function from using panic to using t.Fatal on the provided unit test interface.
81+
As an alternative to [testify/require](https://pkg.go.dev/github.com/stretchr/testify/require) that allows value chaining.
82+
83+
For the functions that accept `debug ...any`, if the first debug value supports t.Fatal and t.Helper,
84+
then panic will be changed to calling t.Fatal instead. (This is experimental)
85+
86+
Use of testify or other alterative targeting unit testing should be preferred over must, expectably outside of test setup.
87+
7888
### Panic test helpers
7989

8090
`must.Panic` and `must.Recover`. Useful for writing tests for panicky cases.

error.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ func NoError(err error) {
1111

1212
// Value panics on error, otherwise returns the first value.
1313
//
14-
// Use this instead of writing a Must version of your function.
14+
// You can use this instead of ignoring errors that never happen by contract.
1515
func Value[T any](out T, err error) T {
1616
if err != nil {
1717
panic(err)

panic.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ func Panic(f func(), debug ...any) (r any) {
1515

1616
func panicWith(debug []any, last any) {
1717
if len(debug) > 0 {
18+
if t, ok := debug[0].(TBSubset); ok {
19+
t.Helper()
20+
if last == nil {
21+
t.Fatal(debug...)
22+
}
23+
t.Fatal(append(debug, last))
24+
}
1825
if last == nil {
1926
panic(debug)
2027
}

testing.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package must
2+
3+
// TBSubset is a subset of testing.TB, TBSubset may add or remove public methods that are defined in testing.TB
4+
// without updating major version. TBSubset should only used to accept testing.TB without depending on testing in this package.
5+
// It also severs as documentation to what features of testing.TB must.*T# functions depends on.
6+
type TBSubset interface {
7+
// The method comments are copied from *testing.T
8+
9+
// Helper marks the calling function as a test helper function.
10+
// When printing file and line information, that function will be skipped.
11+
// Helper may be called simultaneously from multiple goroutines.
12+
Helper()
13+
// Fatal is equivalent to Log followed by FailNow.
14+
//
15+
// Log formats its arguments using default formatting, analogous to Println, and records the text in the error log. For tests, the text will be printed only if the test fails or the -test.v flag is set. For benchmarks, the text is always printed to avoid having performance depend on the value of the -test.v flag.
16+
//
17+
// FailNow marks the function as having failed and stops its execution by calling runtime.Goexit (which then runs all deferred calls in the current goroutine). Execution will continue at the next test or benchmark. FailNow must be called from the goroutine running the test or benchmark function, not from other goroutines created during the test. Calling FailNow does not stop those other goroutines.
18+
Fatal(args ...any)
19+
}
20+
21+
// NoErrorT and alike *T# functions provide test fail instead of panic version of must.
22+
//
23+
// NoErrorT's signature follow other test fail functions for consistency.
24+
func NoErrorT(err error) func(TBSubset) {
25+
return func(t TBSubset) {
26+
if err != nil {
27+
t.Helper()
28+
t.Fatal(err)
29+
}
30+
}
31+
}
32+
33+
// ValueT and alike *T# functions provide test fail instead of panic version of must.
34+
//
35+
// The API has accepts TBSubset in a separate function call to allow function chaining,
36+
// It can not go first because of limitations in go type inference. It can not be encapsulated
37+
// in an interface because interface methods can not introduce generic parameters.
38+
func ValueT[T any](out T, err error) func(TBSubset) T {
39+
return func(t TBSubset) T {
40+
if err != nil {
41+
t.Helper()
42+
t.Fatal(err)
43+
}
44+
return out
45+
}
46+
}
47+
48+
// VT is short for ValueT.
49+
//
50+
// ValueT and alike *T# functions provide test fail instead of panic version of must.
51+
func VT[T any](out T, err error) func(TBSubset) T {
52+
return ValueT(out, err)
53+
}
54+
55+
// ValueT2 and alike *T# functions provide test fail instead of panic version of must.
56+
func ValueT2[T any, U any](out1 T, out2 U, err error) func(TBSubset) (T, U) {
57+
return func(t TBSubset) (T, U) {
58+
if err != nil {
59+
t.Helper()
60+
t.Fatal(err)
61+
}
62+
return out1, out2
63+
}
64+
}
65+
66+
// VT2 is short for ValueT2.
67+
//
68+
// ValueT2 and alike *T# functions provide test fail instead of panic version of must.
69+
func VT2[T any, U any](out1 T, out2 U, err error) func(TBSubset) (T, U) {
70+
return ValueT2(out1, out2, err)
71+
}

0 commit comments

Comments
 (0)