Skip to content

Commit

Permalink
feat: add testing.T#Cleanup() support (#12)
Browse files Browse the repository at this point in the history
## Summary

This pull request introduces a wrapper for `*rapid.T` called `rapidT` to
address compatibility issues with the `TestingT` interface. The changes
include adding a `Cleanup()` method to the `TestingT` interface and
implementing the `rapidT` wrapper with a stub for the `Cleanup()`
method. The wrapper is used in `runScenario` function to maintain
compatibility and provide better integration between `rapid` and
`testing` packages.

*NOTE: step definition signatures using `*rapid.T` are unaffected (i.e.
backwards compatible change)*

## Rationale

We have some non-trivial, common test helpers that occasionally reach
for `t.Cleanup(...)`. I acknowledge that the `After` is intended for
suite cleanup but I think this is an equally valid case.
  • Loading branch information
bryanchriswhite authored Jul 27, 2023
1 parent 2ec3df5 commit 8dd45c4
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 9 deletions.
7 changes: 7 additions & 0 deletions features/hooks.feature
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@ Feature: hooks
And expect scenario tag "@long"
# And step level resources are cleaned up
# And after all tests are done resources are closed

Scenario: testingT cleanup
When I open a resource with cleanup
Then it is open
And expect scenario name "testingT cleanup"
# And step level resources are cleaned up
# And after all tests are done resources are closed
20 changes: 16 additions & 4 deletions hooks_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package gocuke

import (
"testing"

"gotest.tools/v3/assert"
"pgregory.net/rapid"
"testing"
)

func TestHooks(t *testing.T) {
Expand All @@ -18,7 +19,7 @@ func TestHooks(t *testing.T) {
assert.Assert(t, !shortRun)

if open != 0 {
t.Fatalf("expected resource to be closed")
t.Fatalf("expected 0 open resources, got: %d", open)
}

NewRunner(t, &hooksSuite{}).
Expand All @@ -30,7 +31,7 @@ func TestHooks(t *testing.T) {
assert.Assert(t, shortRun)

if open != 0 {
t.Fatalf("expected resource to be closed")
t.Fatalf("expected 0 open resources, got: %d", open)
}

}
Expand All @@ -43,6 +44,7 @@ type hooksSuite struct {
TestingT
numOpenForScenario int64
numOpenForStep int64
numOpenForCleanup int64
scenario Scenario
}

Expand All @@ -59,6 +61,16 @@ func (s *hooksSuite) IOpenAnyResources(t *rapid.T) {
open += s.numOpenForScenario
}

func (s *hooksSuite) IOpenAResourceWithCleanup(step Step) {
assert.Equal(s, "I open a resource with cleanup", step.Text())
s.numOpenForScenario = 1
s.numOpenForCleanup = 1
open += s.numOpenForScenario + s.numOpenForCleanup
s.Cleanup(func() {
open -= s.numOpenForCleanup
})
}

func (s *hooksSuite) ItIsOpen(step Step) {
assert.Equal(s, "it is open", step.Text())
if open < s.numOpenForScenario {
Expand Down Expand Up @@ -89,7 +101,7 @@ func (s *hooksSuite) BeforeStep() {

func (s *hooksSuite) AfterStep() {
if s.numOpenForStep != 1 {
s.Fatalf("expected step resources to be 1 before step")
s.Fatalf("expected step resources to be 1 after step, got: %d", s.numOpenForStep)
}
s.numOpenForStep = 0
}
Expand Down
10 changes: 7 additions & 3 deletions run_scenario.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ func (r *docRunner) runScenario(t *testing.T, pickle *messages.Pickle) {
}

if useRapid {
rapid.Check(t, func(t *rapid.T) {
rapid.Check(t, func(rt *rapid.T) {
t := &rapidT{rt}
(&scenarioRunner{
docRunner: r,
t: t,
Expand Down Expand Up @@ -103,9 +104,12 @@ func (r *scenarioRunner) runTestCase() {
}

for _, hook := range r.afterHooks {
if t, ok := r.t.(interface{ Cleanup(func()) }); ok {
switch t := r.t.(type) {
case *rapidT:
defer r.runHook(hook)
case interface{ Cleanup(func()) }:
t.Cleanup(func() { r.runHook(hook) })
} else {
default:
defer r.runHook(hook)
}
}
Expand Down
4 changes: 2 additions & 2 deletions runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ func NewRunner(t *testing.T, suiteType interface{}) *Runner {
},
// *rapid.T
reflect.TypeOf(&rapid.T{}): func(runner *scenarioRunner) interface{} {
if t, ok := runner.t.(*rapid.T); ok {
return t
if t, ok := runner.t.(*rapidT); ok {
return t.T
}
runner.t.Fatalf("expected %T, but got %T", &rapid.T{}, runner.t)
return nil
Expand Down
13 changes: 13 additions & 0 deletions testingt.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package gocuke

import "pgregory.net/rapid"

// TestingT is the common subset of testing methods exposed to test suite
// instances and expected by common assertion and mocking libraries.
type TestingT interface {
Cleanup(func())
Error(args ...interface{})
Errorf(format string, args ...interface{})
Fail()
Expand All @@ -17,3 +20,13 @@ type TestingT interface {
Skipf(format string, args ...interface{})
Helper()
}

// rapidT is a wrapper around `*rapid.T` that stubs missing `TestingT`
// interface members (e.g. `Cleanup()`).
type rapidT struct {
*rapid.T
}

func (rt *rapidT) Cleanup(fn func()) {
rt.Log("WARNING: cleanup called on `*rapid.T`")
}

0 comments on commit 8dd45c4

Please sign in to comment.