Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@

## Introduction

Goal of the `testing` framework is to provide simple and efficient tools to for
Goal of the `testing` framework is to provide simple common patterns for
writing effective unit, component, and integration tests in [`go`][go].

To accomplish this, the `testing` framework provides a couple of extensions for
[`go`][go]'s [`testing`][testing] package that support a simple setup of
strongly isolated and parallel running unit test using [`gomock`][gomock] and/or
[`gock`][gock] that work under various failure scenarios, e.g. panics, and even
in the presence of spawned [`go`-routines][go-routines].
To accomplish this, the `testing` framework provides a couple of extensions
for [`go`][go]'s [`testing`][testing] package that support a simple setup of
strongly isolated and parallel running unit tests using [`gomock`][gomock]
and/or [`gock`][gock] that work under various failure scenarios and in the
presence of spawned [`go`-routines][go-routines].

[go-routines]: <https://go.dev/tour/concurrency>

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.44
0.0.45
17 changes: 14 additions & 3 deletions internal/mock/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,20 @@ var (
Results: []*Params{},
Variadic: true,
}, {
Name: "Panic",
Params: []*Params{{Name: "arg", Type: "any"}},
Results: []*Params{},
Name: "Log",
Params: []*Params{
{Name: "args", Type: "[]any"},
},
Results: []*Params{},
Variadic: true,
}, {
Name: "Logf",
Params: []*Params{
{Name: "format", Type: "string"},
{Name: "args", Type: "[]any"},
},
Results: []*Params{},
Variadic: true,
}}

methodsGoMockTestReporter = []*Method{{
Expand Down
48 changes: 35 additions & 13 deletions test/caller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ type Caller struct {
path string
}

// Log is the caller reporter function to capture the callers file and line
// number of the `Log` call.
func (c *Caller) Log(_ ...any) {
_, path, line, _ := runtime.Caller(1)
c.path = path + ":" + strconv.Itoa(line)
}

// Logf is the caller reporter function to capture the callers file and line
// number of the `Logf` call.
func (c *Caller) Logf(_ string, _ ...any) {
_, path, line, _ := runtime.Caller(1)
c.path = path + ":" + strconv.Itoa(line)
}

// Error is the caller reporter function to capture the callers file and line
// number of the `Error` call.
func (c *Caller) Error(_ ...any) {
Expand Down Expand Up @@ -76,8 +90,8 @@ func (c *Caller) Panic(_ any) {

// getCaller implements the capturing logic for the callers file and line
// number for the given call.
func getCaller(call func(t test.Reporter)) string {
t := test.New(&testing.T{}, test.Failure, false)
func getCaller(call func(t test.Panicer)) string {
t := test.New(&testing.T{}, false).Expect(test.Failure)
mocks := mock.NewMocks(t)
caller := mock.Get(mocks,
func(*gomock.Controller) *Caller {
Expand All @@ -92,48 +106,56 @@ func getCaller(call func(t test.Reporter)) string {
}

var (
// CallerLog provides the file with line number of the `Log` call.
CallerLog = getCaller(func(t test.Panicer) {
t.Log("log")
})
// CallerLogf provides the file with line number of the `Logf` call.
CallerLogf = getCaller(func(t test.Panicer) {
t.Logf("%s", "log")
})
// CallerError provides the file with line number of the `Error` call.
CallerError = getCaller(func(t test.Reporter) {
CallerError = getCaller(func(t test.Panicer) {
t.Error("fail")
})
// CallerErrorf provides the file with line number of the `Errorf` call.
CallerErrorf = getCaller(func(t test.Reporter) {
CallerErrorf = getCaller(func(t test.Panicer) {
t.Errorf("%s", "fail")
})
// CallerFatal provides the file with line number of the `Fatal` call.
CallerFatal = getCaller(func(t test.Reporter) {
CallerFatal = getCaller(func(t test.Panicer) {
t.Fatal("fail")
})
// CallerFatalf provides the file with line number of the `Fatalf` call.
CallerFatalf = getCaller(func(t test.Reporter) {
CallerFatalf = getCaller(func(t test.Panicer) {
t.Fatalf("%s", "fail")
})
// CallerFail provides the file with line number of the `Fail` call.
CallerFail = getCaller(func(t test.Reporter) {
CallerFail = getCaller(func(t test.Panicer) {
t.Fail()
})
// CallerFailNow provides the file with line number of the `FailNow` call.
CallerFailNow = getCaller(func(t test.Reporter) {
CallerFailNow = getCaller(func(t test.Panicer) {
t.FailNow()
})
// CallerPanic provides the file with line number of the `FailNow` call.
CallerPanic = getCaller(func(t test.Reporter) {
CallerPanic = getCaller(func(t test.Panicer) {
t.Panic("fail")
})

// Generic source directory for caller path evaluation.
SourceDir = test.Must(os.Getwd())
// CallerTestError provides the file with the line number of the `Error`
// call in the test context implementation.
CallerTestError = path.Join(SourceDir, "context.go:352")
CallerTestError = path.Join(SourceDir, "context.go:344")
// CallerReporterErrorf provides the file with the line number of the
// `Errorf` call in the test reporter/validator implementation.
CallerReporterError = path.Join(SourceDir, "reporter.go:87")
CallerReporterError = path.Join(SourceDir, "reporter.go:135")

// CallerTestErrorf provides the file with the line number of the `Errorf`
// call in the test context implementation.
CallerTestErrorf = path.Join(SourceDir, "context.go:370")
CallerTestErrorf = path.Join(SourceDir, "context.go:362")
// CallerReporterErrorf provides the file with the line number of the
// `Errorf` call in the test reporter/validator implementation.
CallerReporterErrorf = path.Join(SourceDir, "reporter.go:109")
CallerReporterErrorf = path.Join(SourceDir, "reporter.go:158")
)
62 changes: 32 additions & 30 deletions test/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
// Test is a minimal interface for abstracting test methods that are needed to
// setup an isolated test environment for GoMock and Testify.
type Test interface { //nolint:interfacebloat // Minimal interface.
// Embeds the basic test reporter interface.
Reporter
// Name provides the test name.
Name() string
// Helper declares a test helper function.
Expand All @@ -38,33 +40,13 @@ type Test interface { //nolint:interfacebloat // Minimal interface.
SkipNow()
// Skipped reports whether the test has been skipped.
Skipped() bool
// Log provides a logging function for the test.
Log(args ...any)
// Logf provides a logging function for the test.
Logf(format string, args ...any)
// Error handles a failure messages when a test is supposed to continue.
Error(args ...any)
// Errorf handles a failure messages when a test is supposed to continue.
Errorf(format string, args ...any)
// Fatal handles a fatal failure message that immediate aborts of the test
// execution.
Fatal(args ...any)
// Fatalf handles a fatal failure message that immediate aborts of the test
// execution.
Fatalf(format string, args ...any)
// Fail handles a failure message that immediate aborts of the test
// execution.
Fail()
// FailNow handles fatal failure notifications without log output that
// aborts test execution immediately.
FailNow()
// Failed reports whether the test has failed.
Failed() bool
// Cleanup is a function called to setup test cleanup after execution.
Cleanup(cleanup func())
}

// Cleanuper defines an interface to add a custom mehtod that is called after
// Cleanuper defines an interface to add a custom method that is called after
// the test execution to cleanup the test environment.
type Cleanuper interface {
Cleanup(cleanup func())
Expand All @@ -80,7 +62,7 @@ func Run(expect Expect, test Func) func(*testing.T) {
return func(t *testing.T) {
t.Helper()

New(t, expect, Parallel).Run(test)
New(t, Parallel).Expect(expect).Run(test)
}
}

Expand All @@ -91,7 +73,7 @@ func RunSeq(expect Expect, test Func) func(*testing.T) {
return func(t *testing.T) {
t.Helper()

New(t, expect, !Parallel).Run(test)
New(t, !Parallel).Expect(expect).Run(test)
}
}

Expand All @@ -102,7 +84,7 @@ func InRun(expect Expect, test Func) Func {
return func(t Test) {
t.Helper()

New(t, expect, !Parallel).Run(test)
New(t, !Parallel).Expect(expect).Run(test)
}
}

Expand All @@ -123,14 +105,14 @@ type Context struct {
}

// New creates a new minimal isolated test context based on the given test
// context with the given expectation. The parent test context is used to
// delegate methods calls to the parent context to propagate test results.
func New(t Test, expect Expect, parallel bool) *Context {
// context with. The parent test context is used to delegate methods calls
// to the parent context to propagate test results.
func New(t Test, parallel bool) *Context {
if tx, ok := t.(*Context); ok {
return &Context{
t: tx, wg: tx.wg,
deadline: tx.deadline,
expect: expect,
expect: true,
parallel: parallel,
}
}
Expand All @@ -142,11 +124,23 @@ func New(t Test, expect Expect, parallel bool) *Context {
deadline, _ := t.Deadline()
return deadline
}(t),
expect: expect,
expect: true,
parallel: parallel,
}
}

// Expect sets up a new test outcome.
func (t *Context) Expect(expect Expect) *Context {
t.t.Helper()

t.mu.Lock()
defer t.mu.Unlock()

t.expect = expect

return t
}

// Timeout sets up an individual timeout for the test. This does not affect the
// global test timeout or a pending parent timeout that may abort the test, if
// the given duration is exceeding the timeout. A negative or zero duration is
Expand Down Expand Up @@ -325,6 +319,9 @@ func (t *Context) Log(args ...any) {
t.t.Helper()

t.t.Log(args...)
if t.reporter != nil {
t.reporter.Log(args...)
}
}

// Logf delegates request to the parent context. It provides a logging function
Expand All @@ -333,6 +330,9 @@ func (t *Context) Logf(format string, args ...any) {
t.t.Helper()

t.t.Logf(format, args...)
if t.reporter != nil {
t.reporter.Logf(format, args...)
}
}

// Error handles failure messages where the test is supposed to continue. On
Expand Down Expand Up @@ -466,7 +466,9 @@ func (t *Context) Panic(arg any) {
stack := regexPanic.Split(string(debug.Stack()), -1)
t.t.Fatalf("panic: %v\n%s\n%s", arg, stack[0], stack[1])
} else if t.reporter != nil {
t.reporter.Panic(arg)
if reporter, ok := t.reporter.(Panicer); ok {
reporter.Panic(arg)
}
}
runtime.Goexit()
}
Expand Down
20 changes: 10 additions & 10 deletions test/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ func TestContext(t *testing.T) {
mock.NewMocks(t).Expect(param.setup)

// When
test.New(t, test.Success, !test.Parallel).
Run(param.test)
test.New(t, !test.Parallel).
Expect(test.Success).Run(param.test)
}))
}
}
Expand Down Expand Up @@ -129,8 +129,8 @@ func TestCleanup(t *testing.T) {
t.Cleanup(func() { wg.Wait() })

// When
test.New(t, test.Success, test.Parallel).
Run(param.test)
test.New(t, test.Parallel).
Expect(test.Success).Run(param.test)

// Then
defer wg.Done()
Expand Down Expand Up @@ -208,11 +208,11 @@ func TestContextParallel(t *testing.T) {
}

// When
test.New(t, test.Success, param.parallel).
Run(func(t test.Test) {
mock.NewMocks(t).Expect(param.setup)
param.during(t)
})
test.New(t, param.parallel).
Expect(test.Success).Run(func(t test.Test) {
mock.NewMocks(t).Expect(param.setup)
param.during(t)
})
}))
}
}
Expand Down Expand Up @@ -273,7 +273,7 @@ func TestDeadline(t *testing.T) {
Run(func(t test.Test, param DeadlineParams) {
mock.NewMocks(t).Expect(param.expect)

test.New(t, !param.failure, !test.Parallel).
test.New(t, !test.Parallel).Expect(!param.failure).
Timeout(param.time).StopEarly(param.early).
Run(func(t test.Test) {
// When
Expand Down
Loading
Loading