Skip to content

Commit c1b306c

Browse files
authored
feat: add recover pattern (#130)
Signed-off-by: Tronje Krop <[email protected]>
1 parent 3a81d3b commit c1b306c

File tree

13 files changed

+137
-76
lines changed

13 files changed

+137
-76
lines changed

.github/workflows/build.yaml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,24 @@ jobs:
2525
path-to-profile: ./build/test-all.cover
2626

2727

28-
macos:
29-
runs-on: macos-latest
30-
steps:
31-
- name: Set up Go
32-
uses: actions/setup-go@v5
33-
with:
34-
go-version: 1.25
35-
cache: false
28+
# macos:
29+
# runs-on: macos-latest
30+
# steps:
31+
# - name: Set up Go
32+
# uses: actions/setup-go@v5
33+
# with:
34+
# go-version: 1.25
35+
# cache: false
3636

37-
- name: Checkout code
38-
uses: actions/checkout@v4
37+
# - name: Checkout code
38+
# uses: actions/checkout@v4
3939

40-
- name: Build and tests
41-
env:
42-
BASH_COMPAT: 3.2
43-
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
44-
LANG: en_US.UTF-8
45-
run: make all
40+
# - name: Build and tests
41+
# env:
42+
# BASH_COMPAT: 3.2
43+
# CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
44+
# LANG: en_US.UTF-8
45+
# run: make all
4646

4747

4848
release:

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.0.43
1+
0.0.44

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ require (
1515

1616
require (
1717
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
18-
golang.org/x/mod v0.29.0 // indirect
19-
golang.org/x/sync v0.17.0 // indirect
18+
golang.org/x/mod v0.30.0 // indirect
19+
golang.org/x/sync v0.18.0 // indirect
2020
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
2121
gopkg.in/yaml.v3 v3.0.1 // indirect
2222
)

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
2828
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
2929
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
3030
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
31-
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
32-
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
33-
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
34-
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
31+
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
32+
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
33+
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
34+
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
3535
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
3636
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
3737
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=

gock/controller_test.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,7 @@ func TestController(t *testing.T) {
7575

7676
func TestPanic(t *testing.T) {
7777
// Given
78-
defer func() {
79-
if err := recover(); err == nil {
80-
assert.Fail(t, "did not panic")
81-
}
82-
}()
78+
defer test.Recover(t, "gock not supported by test setup")
8379

8480
// When
8581
gock.NewGock(gomock.NewController(struct{ gomock.TestReporter }{}))

internal/sync/waitgroup_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import (
77
"github.com/stretchr/testify/assert"
88

99
"github.com/tkrop/go-testing/internal/sync"
10+
"github.com/tkrop/go-testing/test"
1011
)
1112

1213
func TestWaitGroup(t *testing.T) {
1314
t.Parallel()
1415

1516
// Given
16-
defer func() { _ = recover() }()
17+
defer test.Recover(t, "sync: negative WaitGroup counter")
1718
wg := sync.NewWaitGroup()
1819

1920
// When
@@ -23,7 +24,7 @@ func TestWaitGroup(t *testing.T) {
2324
wg.Done()
2425

2526
// Then
26-
assert.Fail(t, "not recovered from panic")
27+
assert.Fail(t, "did not panic")
2728
}
2829

2930
func TestLenientWaitGroup(t *testing.T) {

mock/mocks_test.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -557,10 +557,7 @@ var panicTestCases = map[string]PanicParams{
557557
func TestPanic(t *testing.T) {
558558
test.Map(t, panicTestCases).Run(func(t test.Test, param PanicParams) {
559559
// Given
560-
defer func() {
561-
err := recover()
562-
assert.Equal(t, param.expectError, err)
563-
}()
560+
defer test.Recover(t, param.expectError)
564561

565562
// When
566563
MockSetup(t, param.setup)

test/README.md

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -188,44 +188,22 @@ source of the panic, you need to spawn a new unrecovered go-routine.
188188
hard to recreate. Do not try it.
189189

190190

191-
## Test result builder
192-
193-
Setting up tests and comparing test results is most efficient, when you
194-
directly can set up and compare the actual objects. However, this is sometimes
195-
prevented by the objects not being open for construction and having private
196-
states.
197-
198-
To cope with this challenge the `test`-package supports helpers to access, i.e.
199-
read and write, private fields of objects using reflection.
200-
201-
* `test.NewBuilder[...]()` allows constructing a new object from scratch.
202-
* `test.NewGetter(...)` allows reading private fields of an object by name.
203-
* `test.NewSetter(...)` allows writing private fields by name, and finally
204-
* `test.NewAccessor(...)` allows reading and writing of private fields by name.
205-
206-
The following example shows a real world example of how the private properties
207-
of a closed error can be set up using the `test.NewBuilder[...]()`.
208-
209-
```go
210-
err := test.NewBuilder[viper.ConfigFileNotFoundError]().
211-
Set("locations", fmt.Sprintf("%s", "...path...")).
212-
Set("name", "test").Build()
213-
```
191+
## Out-of-the-box test patterns
214192

215-
Similar we can set up input objects with private properties to minimize the
216-
dependencies in the test setup, however, using this features exposes the test
217-
to internal changes.
193+
Currently, the package supports two _out-of-the-box_ test patterns:
218194

195+
1. `test.Main(func())` - allows to test main methods by calling the main
196+
method with arguments in a well controlled test environment.
197+
2. `test.Recover(Test,any)` - allows to check the panic result in simple test
198+
scenarios where `test.Panic(any)` is not applicable.
219199

220-
## Out-of-the-box test patterns
221200

222-
Currently, the package supports only one _out-of-the-box_ test pattern to test
223-
the `main`-methods of commands.
201+
### Main method tests pattern
224202

225-
The pattern executes the `main` method in a separate process to protect the
226-
test execution against `os.Exit` calls while allowing to capture and check the
227-
exit code against the expectation. The following example demonstrates how to
228-
use the pattern to test a `main` method:
203+
The `test.Main(func())` pattern executes the `main` method in a separate test
204+
process to protect the test execution against `os.Exit` calls while allowing to
205+
capture and check the exit code against the expectation. The following example
206+
demonstrates how to use the pattern to test a `main` method:
229207

230208
```go
231209
mainTestCases := map[string]test.MainParams{
@@ -253,4 +231,30 @@ e.g. as follows:
253231
however, it is focused on testing the `main` methods with and without parsing
254232
command line arguments.
255233

234+
**Note:** In certain situations, `test.Main(func())` currently fails to obtain
235+
the coverage metrics for the test execution, since `go test` is using the
236+
standard output to collect results. We are investigating how we can separate
237+
these in the test execution from expected test output.
238+
239+
240+
## Convenience functions
241+
242+
The test package contains a number of convenience functions to simplify the
243+
test setup and apply certain test patterns. Currently, the following functions
244+
currently supported:
245+
246+
* `test.Must[T](T, error) T` - a convenience method for fluent test case setup
247+
that converts an error into a panic.
248+
* `test.Cast[T](T) T` - a convenience method for fluent test case setup that
249+
converts an casting error into a panic compliant with linting requirements.
250+
* `test.Ptr[T](T) *T` - a convenience method for fluent test case setup that
251+
converts a literal value into a pointer.
252+
* `test.First[T](T, ...any)` - a convenience method for fluent test case setup
253+
that extracts the first value of a response ignoring the others.
254+
255+
Please also have a look at the convenience functions provided by the
256+
[reflect](../reflect) package, that allows you to fluently access non-exported
257+
fields for setting up and checking.
258+
259+
256260
[gomock]: <https://go.uber.org/mock>

test/caller_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,12 @@ var (
128128
CallerTestError = path.Join(SourceDir, "context.go:352")
129129
// CallerReporterErrorf provides the file with the line number of the
130130
// `Errorf` call in the test reporter/validator implementation.
131-
CallerReporterError = path.Join(SourceDir, "reporter.go:83")
131+
CallerReporterError = path.Join(SourceDir, "reporter.go:87")
132132

133133
// CallerTestErrorf provides the file with the line number of the `Errorf`
134134
// call in the test context implementation.
135135
CallerTestErrorf = path.Join(SourceDir, "context.go:370")
136136
// CallerReporterErrorf provides the file with the line number of the
137137
// `Errorf` call in the test reporter/validator implementation.
138-
CallerReporterErrorf = path.Join(SourceDir, "reporter.go:105")
138+
CallerReporterErrorf = path.Join(SourceDir, "reporter.go:109")
139139
)

test/pattern.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ func Ptr[T any](v T) *T {
4646
// others arguments. The method allows to write concise test setup code.
4747
func First[T any](arg T, _ ...any) T { return arg }
4848

49+
// Recover is a convenience method to be used in deferred calls to verify that
50+
// a panic occurred with the expected panic response. The function creates a
51+
// test failure if no panic occurred or the panic response does not match the
52+
// expected value.
53+
func Recover(t Test, expect any) {
54+
// revive:disable-next-line:defer // caller is expected to use defer.
55+
if actual := recover(); actual != nil {
56+
assert.Equal(t, expect, actual)
57+
} else {
58+
assert.Fail(t, "did not panic: %#v", expect)
59+
}
60+
}
61+
4962
// TODO: consider following convenience methods:
5063
//
5164
// // Check is a convenience method that returns the second argument and swallows

0 commit comments

Comments
 (0)