feat(mocktesting): initial implementation

This commit is contained in:
2021-11-21 22:03:11 +00:00
parent 7209ef72c6
commit 5b84721b10
11 changed files with 3564 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
bin/*
coverage.out

205
Makefile Normal file
View File

@@ -0,0 +1,205 @@
GOMODNAME := $(shell grep 'module' go.mod | sed -e 's/^module //')
SOURCES := $(shell find . -name "*.go" -or -name "go.mod" -or -name "go.sum" \
-or -name "Makefile")
# Verbose output
ifdef VERBOSE
V = -v
endif
#
# Environment
#
BINDIR := bin
TOOLDIR := $(BINDIR)/tools
# Global environment variables for all targets
SHELL ?= /bin/bash
SHELL := env \
GO111MODULE=on \
GOBIN=$(CURDIR)/$(TOOLDIR) \
CGO_ENABLED=1 \
PATH='$(CURDIR)/$(BINDIR):$(CURDIR)/$(TOOLDIR):$(PATH)' \
$(SHELL)
#
# Defaults
#
# Default target
.DEFAULT_GOAL := test
#
# Tools
#
TOOLS += $(TOOLDIR)/gobin
$(TOOLDIR)/gobin:
GO111MODULE=off go get -u github.com/myitcv/gobin
# external tool
define tool # 1: binary-name, 2: go-import-path
TOOLS += $(TOOLDIR)/$(1)
$(TOOLDIR)/$(1): $(TOOLDIR)/gobin Makefile
gobin $(V) "$(2)"
endef
$(eval $(call tool,godoc,golang.org/x/tools/cmd/godoc))
$(eval $(call tool,gofumpt,mvdan.cc/gofumpt))
$(eval $(call tool,goimports,golang.org/x/tools/cmd/goimports))
$(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.43))
$(eval $(call tool,gomod,github.com/Helcaraxan/gomod))
.PHONY: tools
tools: $(TOOLS)
#
# Development
#
BENCH ?= .
TESTARGS ?=
.PHONY: clean
clean:
rm -f $(TOOLS)
rm -f ./coverage.out ./go.mod.tidy-check ./go.sum.tidy-check
.PHONY: test
test:
go test $(V) -count=1 -race $(TESTARGS) ./...
.PHONY: test-deps
test-deps:
go test all
.PHONY: lint
lint: $(TOOLDIR)/golangci-lint
golangci-lint $(V) run
.PHONY: format
format: $(TOOLDIR)/goimports $(TOOLDIR)/gofumpt
goimports -w . && gofumpt -w .
.SILENT: bench
.PHONY: bench
bench:
go test $(V) -count=1 -bench=$(BENCH) $(TESTARGS) ./...
.PHONY: golden-update
golden-update:
GOLDEN_UPDATE=1 $(MAKE) test
.PHONY: golden-clean
golden-clean:
find . -type f -name '*.golden' -path '*/testdata/*' -delete
find . -type d -empty -path '*/testdata/*' -delete
.PHONY: golden-regen
golden-regen: golden-clean golden-update
#
# Code Generation
#
.PHONY: generate
generate:
go generate ./...
.PHONY: check-generate
check-generate:
$(eval CHKDIR := $(shell mktemp -d))
cp -av . "$(CHKDIR)"
make -C "$(CHKDIR)/" generate
( diff -rN . "$(CHKDIR)" && rm -rf "$(CHKDIR)" ) || \
( rm -rf "$(CHKDIR)" && exit 1 )
#
# Coverage
#
.PHONY: cov
cov: coverage.out
.PHONY: cov-html
cov-html: coverage.out
go tool cover -html=./coverage.out
.PHONY: cov-func
cov-func: coverage.out
go tool cover -func=./coverage.out
coverage.out: $(SOURCES)
go test $(V) -covermode=count -coverprofile=./coverage.out ./...
#
# Dependencies
#
.PHONY: deps
deps:
go mod download
.PHONY: deps-update
deps-update:
go get -u -t ./...
.PHONY: deps-analyze
deps-analyze: $(TOOLDIR)/gomod
gomod analyze
.PHONY: tidy
tidy:
go mod tidy $(V)
.PHONY: verify
verify:
go mod verify
.SILENT: check-tidy
.PHONY: check-tidy
check-tidy:
cp go.mod go.mod.tidy-check
cp go.sum go.sum.tidy-check
go mod tidy
( \
diff go.mod go.mod.tidy-check && \
diff go.sum go.sum.tidy-check && \
rm -f go.mod go.sum && \
mv go.mod.tidy-check go.mod && \
mv go.sum.tidy-check go.sum \
) || ( \
rm -f go.mod go.sum && \
mv go.mod.tidy-check go.mod && \
mv go.sum.tidy-check go.sum; \
exit 1 \
)
#
# Documentation
#
# Serve docs
.PHONY: docs
docs: $(TOOLDIR)/godoc
$(info serviing docs on http://127.0.0.1:6060/pkg/$(GOMODNAME)/)
@godoc -http=127.0.0.1:6060
#
# Release
#
.PHONY: new-version
new-version: check-npx
npx standard-version
.PHONY: next-version
next-version: check-npx
npx standard-version --dry-run
.PHONY: check-npx
check-npx:
$(if $(shell which npx),,\
$(error No npx found in PATH, please install NodeJS))

189
example_test.go Normal file
View File

@@ -0,0 +1,189 @@
package mocktesting_test
import (
"fmt"
"strings"
"testing"
"github.com/jimeh/go-mocktesting"
)
func Example_basic() {
assertTrue := func(t testing.TB, v bool) {
if v != true {
t.Error("expected false to be true")
}
}
mt := mocktesting.NewT("TestMyBoolean1")
assertTrue(mt, true)
fmt.Printf("Name: %s\n", mt.Name())
fmt.Printf("Failed: %+v\n", mt.Failed())
fmt.Printf("Aborted: %+v\n", mt.Aborted())
mt = mocktesting.NewT("TestMyBoolean2")
assertTrue(mt, false)
fmt.Printf("Name: %s\n", mt.Name())
fmt.Printf("Failed: %+v\n", mt.Failed())
fmt.Printf("Aborted: %+v\n", mt.Aborted())
fmt.Printf("Output: %s\n", strings.Join(mt.Output(), ""))
// Output:
// Name: TestMyBoolean1
// Failed: false
// Aborted: false
// Name: TestMyBoolean2
// Failed: true
// Aborted: false
// Output: expected false to be true
}
func Example_fatal() {
requireTrue := func(t testing.TB, v bool) {
if v != true {
t.Fatal("expected false to be true")
}
}
mt := mocktesting.NewT("TestMyBoolean1")
requireTrue(mt, true)
fmt.Printf("Name: %s\n", mt.Name())
fmt.Printf("Failed: %+v\n", mt.Failed())
fmt.Printf("Aborted: %+v\n", mt.Aborted())
mt = mocktesting.NewT("TestMyBoolean2")
mocktesting.Go(func() {
requireTrue(mt, false)
fmt.Println("This is never executed.")
})
fmt.Printf("Name: %s\n", mt.Name())
fmt.Printf("Failed: %+v\n", mt.Failed())
fmt.Printf("Aborted: %+v\n", mt.Aborted())
fmt.Printf("Output: %s\n", strings.Join(mt.Output(), ""))
// Output:
// Name: TestMyBoolean1
// Failed: false
// Aborted: false
// Name: TestMyBoolean2
// Failed: true
// Aborted: true
// Output: expected false to be true
}
func Example_subtests() {
requireTrue := func(t testing.TB, v bool) {
if v != true {
t.Fatal("expected false to be true")
}
}
mt := mocktesting.NewT("TestMyBoolean")
mt.Run("true", func(t testing.TB) {
requireTrue(t, true)
})
fmt.Printf("Name: %s\n", mt.Name())
fmt.Printf("Failed: %+v\n", mt.Failed())
fmt.Printf("Sub1-Name: %s\n", mt.Subtests()[0].Name())
fmt.Printf("Sub1-Failed: %+v\n", mt.Subtests()[0].Failed())
fmt.Printf("Sub1-Aborted: %+v\n", mt.Subtests()[0].Aborted())
mt.Run("false", func(t testing.TB) {
requireTrue(t, false)
fmt.Println("This is never executed.")
})
fmt.Printf("Failed: %+v\n", mt.Failed())
fmt.Printf("Sub2-Name: %s\n", mt.Subtests()[1].Name())
fmt.Printf("Sub2-Failed: %+v\n", mt.Subtests()[1].Failed())
fmt.Printf("Sub2-Aborted: %+v\n", mt.Subtests()[1].Aborted())
fmt.Printf("Sub2-Output: %s\n", strings.Join(mt.Subtests()[1].Output(), ""))
// Output:
// Name: TestMyBoolean
// Failed: false
// Sub1-Name: TestMyBoolean/true
// Sub1-Failed: false
// Sub1-Aborted: false
// Failed: true
// Sub2-Name: TestMyBoolean/false
// Sub2-Failed: true
// Sub2-Aborted: true
// Sub2-Output: expected false to be true
}
func Example_subtests_in_subtests() {
assertGreaterThan := func(t testing.TB, got int, min int) {
if got <= min {
t.Errorf("expected %d to be greater than %d", got, min)
}
}
mt := mocktesting.NewT("TestMyBoolean")
mt.Run("positive", func(t testing.TB) {
subMT, _ := t.(*mocktesting.T)
subMT.Run("greater than", func(t testing.TB) {
assertGreaterThan(t, 5, 4)
})
subMT.Run("equal", func(t testing.TB) {
assertGreaterThan(t, 5, 5)
})
subMT.Run("less than", func(t testing.TB) {
assertGreaterThan(t, 4, 5)
})
})
fmt.Printf("Name: %s\n", mt.Name())
fmt.Printf("Failed: %+v\n", mt.Failed())
fmt.Printf("Sub1-Name: %s\n", mt.Subtests()[0].Name())
fmt.Printf("Sub1-Failed: %+v\n", mt.Subtests()[0].Failed())
fmt.Printf("Sub1-Aborted: %+v\n", mt.Subtests()[0].Aborted())
fmt.Printf("Sub1-Sub1-Name: %s\n", mt.Subtests()[0].Subtests()[0].Name())
fmt.Printf(
"Sub1-Sub1-Failed: %+v\n", mt.Subtests()[0].Subtests()[0].Failed(),
)
fmt.Printf(
"Sub1-Sub1-Aborted: %+v\n", mt.Subtests()[0].Subtests()[0].Aborted(),
)
fmt.Printf("Sub1-Sub1-Name: %s\n", mt.Subtests()[0].Subtests()[1].Name())
fmt.Printf(
"Sub1-Sub2-Failed: %+v\n", mt.Subtests()[0].Subtests()[1].Failed(),
)
fmt.Printf(
"Sub1-Sub2-Aborted: %+v\n", mt.Subtests()[0].Subtests()[1].Aborted(),
)
fmt.Printf(
"Sub1-Sub3-Output: %s\n", strings.TrimSpace(
strings.Join(mt.Subtests()[0].Subtests()[1].Output(), ""),
),
)
fmt.Printf("Sub1-Sub1-Name: %s\n", mt.Subtests()[0].Subtests()[2].Name())
fmt.Printf(
"Sub1-Sub3-Failed: %+v\n", mt.Subtests()[0].Subtests()[2].Failed(),
)
fmt.Printf(
"Sub1-Sub3-Aborted: %+v\n", mt.Subtests()[0].Subtests()[2].Aborted(),
)
fmt.Printf(
"Sub1-Sub3-Output: %s\n", strings.TrimSpace(
strings.Join(mt.Subtests()[0].Subtests()[2].Output(), ""),
),
)
// Output:
// Name: TestMyBoolean
// Failed: true
// Sub1-Name: TestMyBoolean/positive
// Sub1-Failed: true
// Sub1-Aborted: false
// Sub1-Sub1-Name: TestMyBoolean/positive/greater_than
// Sub1-Sub1-Failed: false
// Sub1-Sub1-Aborted: false
// Sub1-Sub1-Name: TestMyBoolean/positive/equal
// Sub1-Sub2-Failed: true
// Sub1-Sub2-Aborted: false
// Sub1-Sub3-Output: expected 5 to be greater than 5
// Sub1-Sub1-Name: TestMyBoolean/positive/less_than
// Sub1-Sub3-Failed: true
// Sub1-Sub3-Aborted: false
// Sub1-Sub3-Output: expected 4 to be greater than 5
}

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module github.com/jimeh/go-mocktesting
go 1.15
require github.com/stretchr/testify v1.7.0

11
go.sum Normal file
View File

@@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

23
mocktesting.go Normal file
View File

@@ -0,0 +1,23 @@
// Package mocktesting provides a mock of *testing.T for the purpose of testing
// test helpers.
package mocktesting
import (
"sync"
)
// Go runs the provided function in a new goroutine, and blocks until the
// goroutine has exited.
//
// This is essentially a helper function to avoid aborting the current goroutine
// when a *T instance aborts the goroutine that any of FailNow(), Fatal(),
// Fatalf(), SkipNow(), Skip(), or Skipf() are called from.
func Go(f func()) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
f()
}()
wg.Wait()
}

30
mocktesting_test.go Normal file
View File

@@ -0,0 +1,30 @@
package mocktesting
import (
"sync"
)
func runInGoroutine(f func()) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
f()
}()
wg.Wait()
}
func stringsUniq(strs []string) []string {
m := map[string]bool{}
for _, s := range strs {
m[s] = true
}
r := make([]string, 0, len(m))
for s := range m {
r = append(r, s)
}
return r
}

426
t.go Normal file
View File

@@ -0,0 +1,426 @@
package mocktesting
import (
"fmt"
"io/ioutil"
"os"
"reflect"
"runtime"
"strings"
"sync"
"testing"
"time"
)
// TestingT is an interface covering *mocktesting.T's internal use of
// *testing.T. See WithTestingT() for more details.
type TestingT interface {
Fatal(args ...interface{})
}
// T is a fake/mock implementation of testing.T. It records all actions
// performed via all methods on the testing.T interface, so they can be
// inspected and asserted.
//
// It is specifically intended for testing test helpers which accept a
// *testing.T or *testing.B, so you can verify that the helpers call Fatal(),
// Error(), etc, as they need.
type T struct {
name string
abort bool
baseTempdir string
testingT TestingT
deadline time.Time
timeout bool
mux sync.RWMutex
skipped bool
failed int
parallel bool
output []string
helpers []string
aborted bool
cleanups []func()
env map[string]string
subtests []*T
tempdirs []string
// subtestNames is used to ensure subtests do not have conflicting names.
subtestNames map[string]bool
// mkdirTempFunc is used by the TempDir function instead of ioutil.TempDir()
// if it is not nil. This is only used by tests for TempDir itself to ensure
// it behaves correctly if temp directory creation fails.
mkdirTempFunc func(string, string) (string, error)
// Embed *testing.T to implement the testing.TB interface, which has a
// private method to prevent it from being implemented. However that means
// it's very difficult to test testing helpers.
*testing.T
}
// Ensure T struct implements testing.TB interface.
var _ testing.TB = (*T)(nil)
func NewT(name string, options ...Option) *T {
t := &T{
name: strings.ReplaceAll(name, " ", "_"),
abort: true,
baseTempdir: os.TempDir(),
deadline: time.Now().Add(10 * time.Minute),
timeout: true,
}
for _, opt := range options {
opt.apply(t)
}
return t
}
type Option interface {
apply(*T)
}
type optionFunc func(*T)
func (fn optionFunc) apply(g *T) {
fn(g)
}
// WithTimeout specifies a custom timeout for the mock test. It effectively
// determines the return values of Deadline().
//
// When given a zero-value time.Duration, Deadline() will act as if no timeout
// has been set.
//
// If this option is not used, the default timeout value is set to 10 minutes.
func WithTimeout(d time.Duration) Option {
return optionFunc(func(t *T) {
if d > 0 {
t.timeout = true
t.deadline = time.Now().Add(d)
} else {
t.timeout = false
t.deadline = time.Time{}
}
})
}
// WithDeadline specifies a custom timeout for the mock test, but setting the
// deadline to an exact value, rather than setting it based on the offset from
// now of a time.Duration. It effectively determines the return values of
// Deadline().
//
// When given a empty time.Time{}, Deadline() will act as if no timeout has been
// set.
//
// If this option is not used, the default timeout value is set to 10 minutes.
func WithDeadline(d time.Time) Option {
return optionFunc(func(t *T) {
if d != (time.Time{}) {
t.timeout = true
t.deadline = d
} else {
t.timeout = false
t.deadline = time.Time{}
}
})
}
// WithNoAbort disables aborting the current goroutine with runtime.Goexit()
// when SkipNow or FailNow is called. This should be used with care, as it
// causes behavior to diverge from normal *tesing.T, as code after calling
// t.Fatal() will be executed.
func WithNoAbort() Option {
return optionFunc(func(t *T) {
t.abort = false
})
}
// WithBaseTempdir sets the base directory that TempDir() creates temporary
// directories within.
//
// If this option is not used, the default base directory used is os.TempDir().
func WithBaseTempdir(dir string) Option {
return optionFunc(func(t *T) {
if dir != "" {
t.baseTempdir = dir
}
})
}
// WithTestingT accepts a *testing.T instance which is used to report internal
// errors within *mocktesting.T itself. For example if the TempDir() function
// fails to create a temporary directory on disk, it will call Fatal() on the
// *testing.T instance provided here.
//
// If this option is not used, internal errors will instead cause a panic.
func WithTestingT(testingT TestingT) Option {
return optionFunc(func(t *T) {
t.testingT = testingT
})
}
func (t *T) goexit() {
t.aborted = true
if t.abort {
runtime.Goexit()
}
}
func (t *T) internalError(err error) {
err = fmt.Errorf("mocktesting: %w", err)
if t.testingT != nil {
t.testingT.Fatal(err)
} else {
panic(err)
}
}
func (t *T) Name() string {
return t.name
}
func (t *T) Deadline() (time.Time, bool) {
return t.deadline, t.timeout
}
func (t *T) Error(args ...interface{}) {
t.Log(args...)
t.Fail()
}
func (t *T) Errorf(format string, args ...interface{}) {
t.Logf(format, args...)
t.Fail()
}
func (t *T) Fail() {
t.failed++
}
func (t *T) FailNow() {
t.Fail()
t.goexit()
}
func (t *T) Failed() bool {
return t.failed > 0
}
func (t *T) Fatal(args ...interface{}) {
t.Log(args...)
t.FailNow()
}
func (t *T) Fatalf(format string, args ...interface{}) {
t.Logf(format, args...)
t.FailNow()
}
func (t *T) Log(args ...interface{}) {
t.mux.Lock()
defer t.mux.Unlock()
t.output = append(t.output, fmt.Sprintln(args...))
}
func (t *T) Logf(format string, args ...interface{}) {
t.mux.Lock()
defer t.mux.Unlock()
if len(format) == 0 || format[len(format)-1] != '\n' {
format += "\n"
}
t.output = append(t.output, fmt.Sprintf(format, args...))
}
func (t *T) Parallel() {
t.parallel = true
}
func (t *T) Skip(args ...interface{}) {
t.Log(args...)
t.SkipNow()
}
func (t *T) Skipf(format string, args ...interface{}) {
t.Logf(format, args...)
t.SkipNow()
}
func (t *T) SkipNow() {
t.skipped = true
t.goexit()
}
func (t *T) Skipped() bool {
return t.skipped
}
func (t *T) Helper() {
pc, _, _, ok := runtime.Caller(1)
if !ok {
return
}
fnName := runtime.FuncForPC(pc).Name()
t.mux.Lock()
defer t.mux.Unlock()
t.helpers = append(t.helpers, fnName)
}
func (t *T) Cleanup(f func()) {
t.mux.Lock()
defer t.mux.Unlock()
t.cleanups = append(t.cleanups, f)
}
func (t *T) TempDir() string {
// Allow setting MkdirTemp function for testing purposes.
f := t.mkdirTempFunc
if f == nil {
f = ioutil.TempDir
}
dir, err := f(t.baseTempdir, "go-mocktesting*")
if err != nil {
err = fmt.Errorf("TempDir() failed to create directory: %w", err)
t.internalError(err)
}
t.mux.Lock()
defer t.mux.Unlock()
t.tempdirs = append(t.tempdirs, dir)
return dir
}
func (t *T) Run(name string, f func(testing.TB)) bool {
name = t.newSubTestName(name)
fullname := name
if t.name != "" {
fullname = t.name + "/" + name
}
subtest := NewT(fullname)
subtest.abort = t.abort
subtest.baseTempdir = t.baseTempdir
subtest.testingT = t.testingT
subtest.deadline = t.deadline
subtest.timeout = t.timeout
if t.subtestNames == nil {
t.subtestNames = map[string]bool{}
}
t.mux.Lock()
t.subtests = append(t.subtests, subtest)
t.subtestNames[name] = true
t.mux.Unlock()
Go(func() {
f(subtest)
})
if subtest.Failed() {
t.Fail()
}
return !subtest.Failed()
}
func (t *T) newSubTestName(name string) string {
name = strings.ReplaceAll(name, " ", "_")
if !t.subtestNames[name] {
return name
}
i := 1
for {
n := name + "#" + fmt.Sprintf("%02d", i)
if !t.subtestNames[n] {
return n
}
i++
}
}
//
// Inspection Methods which are not part of the testing.TB interface.
//
// Output returns a string slice of all output produced by calls to Log(),
// Logf(), Error(), Errorf(), Fatal(), Fatalf(), Skip(), and Skipf().
func (t *T) Output() []string {
t.mux.RLock()
defer t.mux.RUnlock()
return t.output
}
// CleanupFuncs returns a slice of functions given to Cleanup().
func (t *T) CleanupFuncs() []func() {
t.mux.RLock()
defer t.mux.RUnlock()
return t.cleanups
}
// CleanupNames returns a string slice of function names given to Cleanup().
func (t *T) CleanupNames() []string {
r := make([]string, 0, len(t.cleanups))
for _, f := range t.cleanups {
p := reflect.ValueOf(f).Pointer()
r = append(r, runtime.FuncForPC(p).Name())
}
return r
}
// FailedCount returns the number of times Error(), Errorf(), Fail(), Failf(),
// FailNow(), Fatal(), and Fatalf() were called.
func (t *T) FailedCount() int {
return t.failed
}
// Aborted returns true if the TB instance aborted the current goroutine via
// runtime.Goexit(), which is called by FailNow() and SkipNow().
func (t *T) Aborted() bool {
return t.aborted
}
// HelperNames returns a list of function names which called Helper().
func (t *T) HelperNames() []string {
t.mux.RLock()
defer t.mux.RUnlock()
return t.helpers
}
// Paralleled returns true if Parallel() has been called.
func (t *T) Paralleled() bool {
return t.parallel
}
// Subtests returns a list map of *TB instances for any subtests executed via
// Run().
func (t *T) Subtests() []*T {
if t.subtests == nil {
t.mux.Lock()
t.subtests = []*T{}
t.mux.Unlock()
}
t.mux.RLock()
defer t.mux.RUnlock()
return t.subtests
}

31
t_go116.go Normal file
View File

@@ -0,0 +1,31 @@
//go:build go1.16
// +build go1.16
package mocktesting
func (t *T) Setenv(key string, value string) {
t.mux.Lock()
defer t.mux.Unlock()
if t.env == nil {
t.env = map[string]string{}
}
if key != "" {
t.env[key] = value
}
}
// Getenv returns a map[string]string of keys/values given to Setenv().
func (t *T) Getenv() map[string]string {
if t.env == nil {
t.mux.Lock()
t.env = map[string]string{}
t.mux.Unlock()
}
t.mux.RLock()
defer t.mux.RUnlock()
return t.env
}

182
t_go116_test.go Normal file
View File

@@ -0,0 +1,182 @@
//go:build go1.16
// +build go1.16
package mocktesting
import (
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestT_Setenv(t *testing.T) {
type fields struct {
env map[string]string
}
type args struct {
key string
value string
}
tests := []struct {
name string
fields fields
args args
want map[string]string
}{
{
name: "empty key and value",
args: args{},
want: map[string]string{},
},
{
name: "empty key",
args: args{value: "bar"},
want: map[string]string{},
},
{
name: "empty value",
args: args{key: "foo"},
want: map[string]string{"foo": ""},
},
{
name: "key and value",
args: args{key: "foo", value: "bar"},
want: map[string]string{"foo": "bar"},
},
{
name: "add to existing",
fields: fields{env: map[string]string{"hello": "world"}},
args: args{key: "foo", value: "bar"},
want: map[string]string{"hello": "world", "foo": "bar"},
},
{
name: "overwrite existing",
fields: fields{env: map[string]string{"foo": "world"}},
args: args{key: "foo", value: "bar"},
want: map[string]string{"foo": "bar"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mt := &T{env: tt.fields.env}
mt.Setenv(tt.args.key, tt.args.value)
assert.Equal(t, tt.want, mt.env)
})
}
}
func TestT_Getenv(t *testing.T) {
type fields struct {
env map[string]string
}
tests := []struct {
name string
fields fields
want map[string]string
}{
{
name: "nil env",
want: map[string]string{},
},
{
name: "empty env",
fields: fields{env: map[string]string{}},
want: map[string]string{},
},
{
name: "env",
fields: fields{
env: map[string]string{
"hello": "world",
"foo": "bar",
},
},
want: map[string]string{
"hello": "world",
"foo": "bar",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mt := &T{env: tt.fields.env}
got := mt.Getenv()
assert.Equal(t, tt.want, got)
})
}
}
func TestT_Run_Go116(t *testing.T) {
type fields struct {
name string
abort bool
baseTempdir string
testingT testing.TB
deadline time.Time
timeout bool
}
type args struct {
f func(testing.TB)
}
tests := []struct {
name string
fields fields
args args
want *T
}{
{
name: "set environment variables",
fields: fields{
name: "set_environment_variables",
abort: true,
baseTempdir: os.TempDir(),
deadline: time.Now().Add(10 * time.Minute),
timeout: true,
},
args: args{
f: func(t testing.TB) {
// Type assert to *TB for compatibility with Go 1.16 and
// earlier.
mt := t.(*T)
mt.Setenv("GO_ENV", "test")
mt.Setenv("FOO", "bar")
},
},
want: &T{
name: "set_environment_variables",
abort: true,
baseTempdir: os.TempDir(),
deadline: time.Now().Add(10 * time.Minute),
timeout: true,
env: map[string]string{
"GO_ENV": "test",
"FOO": "bar",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mt := &T{
name: tt.fields.name,
abort: tt.fields.abort,
baseTempdir: tt.fields.baseTempdir,
testingT: tt.fields.testingT,
deadline: tt.fields.deadline,
timeout: tt.fields.timeout,
}
runInGoroutine(func() {
tt.args.f(mt)
})
assertEqualMocktestingT(t, tt.want, mt)
})
}
}

2460
t_test.go Normal file

File diff suppressed because it is too large Load Diff