···3636 - unused
3737 - whitespace
38383939+issues:
4040+ exclude-rules:
4141+ - path: test_test.go
4242+ linters:
4343+ - thelper # The entire package is effectively a t.Helper
4444+ - goconst # Lots of repetition in here
4545+3946linters-settings:
4047 errcheck:
4148 check-type-assertions: true
+19-77
README.md
···42424343 test.True(t, true) // Passes
4444 test.False(t, true) // Fails
4545-4646- // Get $CWD/testdata easily
4747- test.Data(t) // /Users/you/project/package/testdata
4848-4949- // Check against contents of a file including line ending normalisation
5050- file := filepath.Join(test.Data(t), "expected.txt")
5151- test.File(t, "hello\n", file)
5252-5353- // Just like the good old reflect.DeepEqual, but with a nicer format
5454- test.DeepEqual(t, []string{"hello"}, []string{"world"}) // Fails
5545}
5646```
57475858-### Self Documenting Tests
4848+### Add Additional Context
59496060-> [!TIP]
6161-> Line comments on the line you call most `test` functions on will be shown in failure messages as additional context
6262-6363-That means you can have additional context in the failure message, as well as helpful comments explaining the assertion to readers of your code
5050+`test` provides a number of options to decorate your test log with useful context:
64516552```go
6666-func TestSomething(t *testing.T) {
6767- test.Equal(t, "apples", "oranges") // Fruits are not equal
5353+func TestDetail(t *testing.T) {
5454+ test.Equal(t, "apples", "oranges", test.Title("Fruit scramble!"), test.Context("Apples are not oranges!"))
6855}
6956```
70577171-Will get you a failure message like:
5858+Will get you an error log in the test that looks like this...
72597373-```shell
7474---- FAIL: TestSomething (0.00s)
7575- something_test.go:1:
7676- Not Equal // Fruits are not equal
7777- ---------
6060+```plaintext
6161+--- FAIL: TestDemo (0.00s)
6262+ test_test.go:501:
6363+ Fruit scramble!
6464+ ---------------
6565+7866 Got: apples
7967 Wanted: oranges
6868+6969+ (Apples are not oranges!)
7070+7171+FAIL
8072```
81738274### Non Comparable Types
83758484-`test` uses Go 1.18+ generics under the hood for most of the comparison, which is great, but what if your types don't satisfy `comparable`. We also provide
7676+`test` uses generics under the hood for most of the comparison, which is great, but what if your types don't satisfy `comparable`. We also provide
8577`test.EqualFunc` and `test.NotEqualFunc` for those exact situations!
86788779These allow you to pass in a custom comparator function for your type, if your comparator function returns true, the types are considered equal.
···98909991 test.EqualFunc(t, a, b, sliceEqual) // Passes
10092101101- // Can also use e.g. the new slices package
102102- test.EqualFunc(t, a, b, slices.Equal[string]) // Also passes :)
9393+ // Can also use any function here
9494+ test.EqualFunc(t, a, b, slices.Equal) // Also passes :)
10395104104- test.EqualFunc(t, a, c, slices.Equal[string]) // Fails
9696+ test.EqualFunc(t, a, c, slices.Equal) // Fails
10597}
10698```
10799108100You can also use this same pattern for custom user defined types, structs etc.
109109-110110-### Rich Comparison
111111-112112-Large structs or long slices can often be difficult to compare using `reflect.DeepEqual`, you have to scan for the difference yourself. `test` provides a
113113-`test.Diff` function that produces a rich text diff for you on failure:
114114-115115-```go
116116-func TestDiff(t *testing.T) {
117117- // Pretend these are very long, or are large structs
118118- a := []string{"hello", "world"}
119119- b := []string{"hello", "there"}
120120-121121- test.Diff(t, a, b)
122122-}
123123-```
124124-125125-Will give you:
126126-127127-```plain
128128---- FAIL: TestDiff (0.00s)
129129- main_test.go:14: Mismatch (-want, +got):
130130- []string{
131131- "hello",
132132- - "there",
133133- + "world",
134134- }
135135-```
136101137102### Table Driven Tests
138103···231196```
232197233198Under the hood `CaptureOutput` temporarily captures both streams, copies the data to a buffer and returns the output back to you, before cleaning everything back up again.
234234-235235-### Golden Files
236236-237237-`test` has great support for golden files:
238238-239239-```go
240240-func TestFile(t *testing.T) {
241241- got := "some contents\n"
242242- want := filepath.Join(test.Data(t), "golden.txt")
243243-244244- test.File(t, got, want)
245245-}
246246-```
247247-248248-This will read the file, normalise line endings and then generate an output almost like a git diff:
249249-250250-```patch
251251---- want
252252-+++ got
253253-@@ -1 +1 @@
254254--some file contents
255255-+some contents
256256-```
257199258200### Credits
259201
+47-10
Taskfile.yml
···99 default:
1010 desc: List all available tasks
1111 silent: true
1212- cmd: task --list
1212+ cmds:
1313+ - task --list
13141415 tidy:
1516 desc: Tidy dependencies in go.mod and go.sum
1616- cmd: go mod tidy
1717+ sources:
1818+ - "**/*.go"
1919+ - go.mod
2020+ - go.sum
2121+ cmds:
2222+ - go mod tidy
17231824 fmt:
1925 desc: Run go fmt on all source files
2626+ sources:
2727+ - "**/*.go"
2028 preconditions:
2129 - sh: command -v golines
2222- msg: golines not installed, see https://github.com/segmentio/golines
3030+ msg: golines not installed, run `go install github.com/segmentio/golines@latest`
2331 cmds:
2432 - go fmt ./...
2525- - golines . --chain-split-dots --ignore-generated --write-output
3333+ - golines . --ignore-generated --write-output --max-len 120
26342735 test:
2836 desc: Run the test suite
2929- cmd: go test -race ./... {{ .CLI_ARGS }}
3737+ sources:
3838+ - "**/*.go"
3939+ - testdata/**/*.snap.txt
4040+ cmds:
4141+ - go test -race ./... {{ .CLI_ARGS }}
4242+ env:
4343+ NO_COLOR: true
30443145 bench:
3246 desc: Run all project benchmarks
3333- cmd: go test ./... -run None -benchmem -bench . {{ .CLI_ARGS }}
4747+ sources:
4848+ - "**/*.go"
4949+ cmds:
5050+ - go test ./... -run None -benchmem -bench . {{ .CLI_ARGS }}
34513552 lint:
3653 desc: Run the linters and auto-fix if possible
3737- cmd: golangci-lint run --fix
5454+ sources:
5555+ - "**/*.go"
5656+ - .golangci.yml
3857 deps:
3958 - fmt
4059 preconditions:
4160 - sh: command -v golangci-lint
4261 msg: golangci-lint not installed, see https://golangci-lint.run/usage/install/#local-installation
43626363+ - sh: command -v betteralign
6464+ msg: requires betteralign, run `go install github.com/dkorunic/betteralign/cmd/betteralign@latest`
6565+6666+ - sh: command -v typos
6767+ msg: requires typos-cli, run `brew install typos-cli`
6868+ cmds:
6969+ - betteralign -test_files -apply ./...
7070+ - golangci-lint run --fix
7171+ - typos
7272+4473 doc:
4574 desc: Render the pkg docs locally
4646- cmd: pkgsite -open
4775 preconditions:
4876 - sh: command -v pkgsite
4949- msg: pkgsite not installed, run go install golang.org/x/pkgsite/cmd/pkgsite@latest
7777+ msg: pkgsite not installed, run `go install golang.org/x/pkgsite/cmd/pkgsite@latest`
7878+ cmds:
7979+ - pkgsite -open
50805181 cov:
5282 desc: Calculate test coverage and render the html
···64946595 sloc:
6696 desc: Print lines of code
6767- cmd: fd . -e go | xargs wc -l | sort -nr | head
9797+ cmds:
9898+ - fd . -e go | xargs wc -l | sort -nr | head
689969100 clean:
70101 desc: Remove build artifacts and other clutter
71102 cmds:
72103 - go clean ./...
73104 - rm -rf {{ .COV_DATA }}
105105+106106+ update:
107107+ desc: Updates dependencies in go.mod and go.sum
108108+ cmds:
109109+ - go get -u ./...
110110+ - go mod tidy
+142
config.go
···11+package test
22+33+import (
44+ "errors"
55+ "fmt"
66+ "math"
77+ "strings"
88+)
99+1010+const (
1111+ defaultFloatEqualityThreshold = 1e-8
1212+)
1313+1414+// config holds test-specific configuration including additional context
1515+// and how the caller wants this library to behave.
1616+type config struct {
1717+ title string // Title of the test, shown as a header in the failure log
1818+ context string // Additional context passed by the caller
1919+ reason string // Concise reason why the test has failed, only used sparingly and not in a user option
2020+ floatEqualityThreshold float64 // The difference threshold below which two floats are considered equal
2121+}
2222+2323+// defaultConfig returns a default configuration.
2424+func defaultConfig() config {
2525+ return config{
2626+ floatEqualityThreshold: defaultFloatEqualityThreshold,
2727+ }
2828+}
2929+3030+// failure represents a test failure, including any set config.
3131+type failure[T any] struct {
3232+ got T // The actual value
3333+ want T // Expected value
3434+ cfg config // Test config
3535+}
3636+3737+// String implements [fmt.Stringer] for failure, allowing it to print itself in the test log.
3838+func (f failure[T]) String() string {
3939+ s := &strings.Builder{}
4040+ s.WriteByte('\n')
4141+4242+ s.WriteString(f.cfg.title)
4343+ s.WriteByte('\n')
4444+ s.WriteString(strings.Repeat("-", len(f.cfg.title)))
4545+ s.WriteString("\n\n")
4646+4747+ fmt.Fprintf(s, "Got:\t%+v\n", f.got)
4848+ fmt.Fprintf(s, "Wanted:\t%+v\n", f.want)
4949+5050+ if f.cfg.context != "" {
5151+ fmt.Fprintf(s, "\n(%s)\n", f.cfg.context)
5252+ }
5353+5454+ if f.cfg.reason != "" {
5555+ fmt.Fprintf(s, "\nBecause: %s\n", f.cfg.reason)
5656+ }
5757+5858+ return s.String()
5959+}
6060+6161+// Option is a configuration option for a test.
6262+type Option interface {
6363+ // Apply the option to the test config, returning an error if the option
6464+ // cannot be applied for whatever reason.
6565+ apply(*config) error
6666+}
6767+6868+// option is a function adapter implementing the Option interface, analogous
6969+// to how http.HandlerFunc implements the Handler interface.
7070+type option func(*config) error
7171+7272+// apply applies the option, implementing the Option interface for the option
7373+// function adapter.
7474+func (o option) apply(cfg *config) error {
7575+ return o(cfg)
7676+}
7777+7878+// FloatEqualityThreshold is an [Option] to set the maximum difference allowed between
7979+// two floating point numbers before they are considered equal. This setting is only
8080+// used in [NearlyEqual] and [NotNearlyEqual].
8181+//
8282+// Setting threshold to ±math.Inf is an error and will fail the test.
8383+//
8484+// The default is 1e-8, a sensible default for most cases.
8585+func FloatEqualityThreshold(threshold float64) Option {
8686+ f := func(cfg *config) error {
8787+ if math.IsInf(threshold, 0) {
8888+ return errors.New("cannot set floating point equality threshold to ±infinity")
8989+ }
9090+ cfg.floatEqualityThreshold = threshold
9191+ return nil
9292+ }
9393+ return option(f)
9494+}
9595+9696+// Title is an [Option] that sets the title of the test in the test failure log.
9797+//
9898+// The title is shown as an underlined header in the test failure, below which the
9999+// actual and expected values will be shown.
100100+//
101101+// By default this will be named sensibly after the test function being called, for
102102+// example [Equal] has a default title "Not Equal".
103103+//
104104+// Setting title explicitly to the empty string "" is an error and will fail the test.
105105+//
106106+// test.Equal(t, "apples", "oranges", test.Title("Wrong fruits!"))
107107+func Title(title string) Option {
108108+ f := func(cfg *config) error {
109109+ if title == "" {
110110+ return errors.New("cannot set title to an empty string")
111111+ }
112112+ cfg.title = strings.TrimSpace(title)
113113+ return nil
114114+ }
115115+ return option(f)
116116+}
117117+118118+// Context is an [Option] that allows the caller to inject useful contextual information
119119+// as to why the test failed. This can be a useful addition to the test failure output log.
120120+//
121121+// The signature of context allows the use of fmt print verbs to format the message in the
122122+// same way one might use [fmt.Sprintf].
123123+//
124124+// It is not necessary to include a newline character at the end of format.
125125+//
126126+// Setting context explicitly to the empty string "" is an error and will fail the test.
127127+//
128128+// For example:
129129+//
130130+// err := doSomethingComplicated()
131131+// test.Ok(t, err, test.Context("something complicated failed"))
132132+func Context(format string, args ...any) Option {
133133+ f := func(cfg *config) error {
134134+ if format == "" {
135135+ return errors.New("cannot set context to an empty string")
136136+ }
137137+ context := fmt.Sprintf(format, args...)
138138+ cfg.context = strings.TrimSpace(context)
139139+ return nil
140140+ }
141141+ return option(f)
142142+}
···11-// Package test provides a lightweight, but useful extension to the std lib's testing package
22-// with a friendlier and more intuitive API.
11+// Package test provides a lightweight, but useful extension to the std lib's testing package with
22+// a friendlier and more intuitive API.
33+//
44+// Simple tests become trivial and test provides mechanisms for adding useful context to test failures.
35package test
4657import (
66- "bufio"
78 "bytes"
89 "errors"
910 "fmt"
1011 "io"
1112 "math"
1213 "os"
1313- "path/filepath"
1414- "reflect"
1515- "runtime"
1614 "strings"
1715 "sync"
1816 "testing"
19172018 "github.com/FollowTheProcess/test/internal/colour"
2119 "github.com/FollowTheProcess/test/internal/diff"
2222- "github.com/google/go-cmp/cmp"
2320)
2424-2525-// floatEqualityThreshold allows us to do near-equality checks for floats.
2626-const floatEqualityThreshold = 1e-8
2727-2828-// failure represents a test failure, including context and reason.
2929-type failure[T any] struct {
3030- got T // What we got
3131- want T // Expected value
3232- title string // Title of the failure, used as a header
3333- reason string // Optional reason for additional context
3434- comment string // Optional line comment for context
3535-}
3636-3737-// String prints a failure.
3838-func (f failure[T]) String() string {
3939- var msg string
4040- if f.comment != "" {
4141- msg = fmt.Sprintf(
4242- "\n%s // %s\n%s\nGot:\t%+v\nWanted:\t%+v\n",
4343- f.title,
4444- f.comment,
4545- strings.Repeat("-", len(f.title)),
4646- f.got,
4747- f.want,
4848- )
4949- } else {
5050- msg = fmt.Sprintf(
5151- "\n%s\n%s\nGot:\t%+v\nWanted:\t%+v\n",
5252- f.title,
5353- strings.Repeat("-", len(f.title)),
5454- f.got,
5555- f.want,
5656- )
5757- }
5858-5959- if f.reason != "" {
6060- // Bolt the reason on the end
6161- msg = fmt.Sprintf("%s\n%s\n", msg, f.reason)
6262- }
6363-6464- return msg
6565-}
66216722// Equal fails if got != want.
6823//
6924// test.Equal(t, "apples", "apples") // Passes
7025// test.Equal(t, "apples", "oranges") // Fails
7171-func Equal[T comparable](tb testing.TB, got, want T) {
2626+func Equal[T comparable](tb testing.TB, got, want T, options ...Option) {
7227 tb.Helper()
2828+ cfg := defaultConfig()
2929+ cfg.title = "Not Equal"
3030+3131+ for _, option := range options {
3232+ if err := option.apply(&cfg); err != nil {
3333+ tb.Fatalf("Equal: could not apply options: %v", err)
3434+ return
3535+ }
3636+ }
3737+7338 if got != want {
7439 fail := failure[T]{
7575- got: got,
7676- want: want,
7777- title: "Not Equal",
7878- comment: getComment(),
4040+ got: got,
4141+ want: want,
4242+ cfg: cfg,
7943 }
8044 tb.Fatal(fail.String())
8145 }
8246}
83478484-// NearlyEqual is like Equal but for floating point numbers where typically equality often fails.
4848+// NotEqual is the opposite of [Equal], it fails if got == want.
8549//
8686-// If the difference between got and want is sufficiently small, they are considered equal.
5050+// test.NotEqual(t, 10, 42) // Passes
5151+// test.NotEqual(t, 42, 42) // Fails
5252+func NotEqual[T comparable](tb testing.TB, got, want T, options ...Option) {
5353+ tb.Helper()
5454+ cfg := defaultConfig()
5555+ cfg.title = "Equal"
5656+5757+ for _, option := range options {
5858+ if err := option.apply(&cfg); err != nil {
5959+ tb.Fatalf("NotEqual: could not apply options: %v", err)
6060+ return
6161+ }
6262+ }
6363+6464+ if got == want {
6565+ fail := failure[T]{
6666+ got: got,
6767+ want: want,
6868+ cfg: cfg,
6969+ }
7070+ tb.Fatal(fail.String())
7171+ }
7272+}
7373+7474+// EqualFunc is like [Equal] but accepts a custom comparator function, useful
7575+// when the items to be compared do not implement the comparable generic constraint.
7676+//
7777+// The signature of the comparator is such that standard library functions such as
7878+// [slices.Equal] or [maps.Equal] can be used.
7979+//
8080+// The comparator should return true if the two items should be considered equal.
8181+//
8282+// test.EqualFunc(t, []int{1, 2, 3}, []int{1, 2, 3}, slices.Equal) // Passes
8383+// test.EqualFunc(t, []int{1, 2, 3}, []int{4, 5, 6}, slices.Equal) // Fails
8484+func EqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool, options ...Option) {
8585+ tb.Helper()
8686+ cfg := defaultConfig()
8787+ cfg.title = "Not Equal"
8888+8989+ for _, option := range options {
9090+ if err := option.apply(&cfg); err != nil {
9191+ tb.Fatalf("EqualFunc: could not apply options: %v", err)
9292+ return
9393+ }
9494+ }
9595+9696+ if !equal(got, want) {
9797+ cfg.reason = "equal(got, want) returned false"
9898+ fail := failure[T]{
9999+ got: got,
100100+ want: want,
101101+ cfg: cfg,
102102+ }
103103+ tb.Fatal(fail.String())
104104+ }
105105+}
106106+107107+// NotEqualFunc is like [Equal] but accepts a custom comparator function, useful
108108+// when the items to be compared do not implement the comparable generic constraint.
109109+//
110110+// The signature of the comparator is such that standard library functions such as
111111+// [slices.Equal] or [maps.Equal] can be used.
112112+//
113113+// The comparator should return true if the two items should be considered equal.
114114+//
115115+// test.EqualFunc(t, []int{1, 2, 3}, []int{1, 2, 3}, slices.Equal) // Fails
116116+// test.EqualFunc(t, []int{1, 2, 3}, []int{4, 5, 6}, slices.Equal) // Passes
117117+func NotEqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool, options ...Option) {
118118+ tb.Helper()
119119+ cfg := defaultConfig()
120120+ cfg.title = "Equal"
121121+122122+ for _, option := range options {
123123+ if err := option.apply(&cfg); err != nil {
124124+ tb.Fatalf("NotEqualFunc: could not apply options: %v", err)
125125+ return
126126+ }
127127+ }
128128+129129+ if equal(got, want) {
130130+ cfg.reason = "equal(got, want) returned true"
131131+ fail := failure[T]{
132132+ got: got,
133133+ want: want,
134134+ cfg: cfg,
135135+ }
136136+ tb.Fatal(fail.String())
137137+ }
138138+}
139139+140140+// NearlyEqual is like [Equal] but for floating point numbers where absolute equality often fails.
141141+//
142142+// If the difference between got and want is sufficiently small, they are considered equal. This threshold
143143+// defaults to 1e-8 but can be configured with the [FloatEqualityThreshold] option.
87144//
88145// test.NearlyEqual(t, 3.0000000001, 3.0) // Passes, close enough to be considered equal
89146// test.NearlyEqual(t, 3.0000001, 3.0) // Fails, too different
9090-func NearlyEqual[T ~float32 | ~float64](tb testing.TB, got, want T) {
147147+func NearlyEqual[T ~float32 | ~float64](tb testing.TB, got, want T, options ...Option) {
91148 tb.Helper()
149149+ cfg := defaultConfig()
150150+ cfg.title = "Not NearlyEqual"
151151+152152+ for _, option := range options {
153153+ if err := option.apply(&cfg); err != nil {
154154+ tb.Fatalf("NearlyEqual: could not apply options: %v", err)
155155+ return
156156+ }
157157+ }
158158+92159 diff := math.Abs(float64(got - want))
9393- if diff >= floatEqualityThreshold {
160160+ if diff > cfg.floatEqualityThreshold {
161161+ cfg.reason = fmt.Sprintf(
162162+ "Difference %v - %v = %v exceeds maximum tolerance of %v",
163163+ got,
164164+ want,
165165+ diff,
166166+ cfg.floatEqualityThreshold,
167167+ )
94168 fail := failure[T]{
9595- got: got,
9696- want: want,
9797- title: "Not NearlyEqual",
9898- reason: fmt.Sprintf(
9999- "Difference %v exceeds maximum tolerance of %v",
100100- diff,
101101- floatEqualityThreshold,
102102- ),
103103- comment: getComment(),
169169+ got: got,
170170+ want: want,
171171+ cfg: cfg,
104172 }
105173 tb.Fatal(fail.String())
106106- }
107107-}
108108-109109-// EqualFunc is like Equal but allows the user to pass a custom comparator, useful
110110-// when the items to be compared do not implement the comparable generic constraint
111111-//
112112-// The comparator should return true if the two items should be considered equal.
113113-func EqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool) {
114114- tb.Helper()
115115- if !equal(got, want) {
116116- fail := failure[T]{
117117- got: got,
118118- want: want,
119119- title: "Not Equal",
120120- reason: "equal(got, want) returned false",
121121- comment: getComment(),
122122- }
123123- tb.Fatal(fail.String())
124124- }
125125-}
126126-127127-// NotEqual fails if got == want.
128128-//
129129-// test.NotEqual(t, "apples", "oranges") // Passes
130130-// test.NotEqual(t, "apples", "apples") // Fails
131131-func NotEqual[T comparable](tb testing.TB, got, want T) {
132132- tb.Helper()
133133- if got == want {
134134- if comment := getComment(); comment != "" {
135135- tb.Fatalf(
136136- "\nEqual // %s\n%s\nGot:\t%+v\n\nExpected values to be different\n",
137137- comment,
138138- strings.Repeat("-", len("Equal")),
139139- got,
140140- )
141141- } else {
142142- tb.Fatalf("\nEqual\n%s\nGot:\t%+v\n\nExpected values to be different\n", strings.Repeat("-", len("Equal")), got)
143143- }
144144- }
145145-}
146146-147147-// NotEqualFunc is like NotEqual but allows the user to pass a custom comparator, useful
148148-// when the items to be compared do not implement the comparable generic constraint
149149-//
150150-// The comparator should return true if the two items should be considered equal.
151151-func NotEqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool) {
152152- tb.Helper()
153153- if equal(got, want) {
154154- if comment := getComment(); comment != "" {
155155- tb.Fatalf(
156156- "\nEqual // %s\n%s\nGot:\t%+v\n\nequal(got, want) returned true\n",
157157- comment,
158158- strings.Repeat("-", len("Equal")),
159159- got,
160160- )
161161- } else {
162162- tb.Fatalf("\nEqual\n%s\nGot:\t%+v\n\nequal(got, want) returned true\n", strings.Repeat("-", len("Equal")), got)
163163- }
164174 }
165175}
166176···168178//
169179// err := doSomething()
170180// test.Ok(t, err)
171171-func Ok(tb testing.TB, err error) {
181181+func Ok(tb testing.TB, err error, options ...Option) {
172182 tb.Helper()
183183+ cfg := defaultConfig()
184184+ cfg.title = "Not Ok"
185185+186186+ for _, option := range options {
187187+ if optionErr := option.apply(&cfg); optionErr != nil {
188188+ tb.Fatalf("Ok: could not apply options: %v", optionErr)
189189+ return
190190+ }
191191+ }
192192+173193 if err != nil {
174194 fail := failure[error]{
175175- got: err,
176176- want: nil,
177177- title: "Not Ok",
178178- comment: getComment(),
195195+ got: err,
196196+ want: nil,
197197+ cfg: cfg,
179198 }
180199 tb.Fatal(fail.String())
181200 }
···183202184203// Err fails if err == nil.
185204//
186186-// err := shouldReturnErr()
205205+// err := shouldFail()
187206// test.Err(t, err)
188188-func Err(tb testing.TB, err error) {
207207+func Err(tb testing.TB, err error, options ...Option) {
189208 tb.Helper()
209209+ cfg := defaultConfig()
210210+ cfg.title = "Not Err"
211211+212212+ for _, option := range options {
213213+ if optionErr := option.apply(&cfg); optionErr != nil {
214214+ tb.Fatalf("Err: could not apply options: %v", optionErr)
215215+ return
216216+ }
217217+ }
218218+190219 if err == nil {
191220 fail := failure[error]{
192192- got: nil,
193193- want: errors.New("error"),
194194- title: "Not Err",
195195- comment: getComment(),
221221+ got: nil,
222222+ want: errors.New("error"),
223223+ cfg: cfg,
196224 }
197225 tb.Fatal(fail.String())
198226 }
199227}
200228201201-// WantErr fails if you got an error and didn't want it, or if you
202202-// didn't get an error but wanted one.
229229+// WantErr fails if you got an error and didn't want it, or if you didn't
230230+// get an error but wanted one.
203231//
204204-// It simplifies checking for errors in table driven tests where on any
205205-// iteration err may or may not be nil.
232232+// It greatly simplifies checking for errors in table driven tests where an error
233233+// may or may not be nil on any given test case.
206234//
207235// test.WantErr(t, errors.New("uh oh"), true) // Passes, got error when we wanted one
208236// test.WantErr(t, errors.New("uh oh"), false) // Fails, got error but didn't want one
209237// test.WantErr(t, nil, true) // Fails, wanted an error but didn't get one
210238// test.WantErr(t, nil, false) // Passes, didn't want an error and didn't get one
211211-func WantErr(tb testing.TB, err error, want bool) {
239239+func WantErr(tb testing.TB, err error, want bool, options ...Option) {
212240 tb.Helper()
241241+ cfg := defaultConfig()
242242+ cfg.title = "WantErr"
243243+244244+ for _, option := range options {
245245+ if optionErr := option.apply(&cfg); optionErr != nil {
246246+ tb.Fatalf("WantErr: could not apply options: %v", optionErr)
247247+ return
248248+ }
249249+ }
250250+213251 if (err != nil) != want {
214214- var reason string
215215- var wanted error
252252+ var (
253253+ reason string
254254+ wanted error
255255+ )
216256 if want {
217257 reason = fmt.Sprintf("Wanted an error but got %v", err)
218258 wanted = errors.New("error")
···220260 reason = fmt.Sprintf("Got an unexpected error: %v", err)
221261 wanted = nil
222262 }
223223- fail := failure[any]{
224224- got: err,
225225- want: wanted,
226226- title: "WantErr",
227227- reason: reason,
228228- comment: getComment(),
263263+ cfg.reason = reason
264264+ fail := failure[error]{
265265+ got: err,
266266+ want: wanted,
267267+ cfg: cfg,
229268 }
230269 tb.Fatal(fail.String())
231270 }
232271}
233272234234-// True fails if v is false.
273273+// True fails if got is false.
235274//
236275// test.True(t, true) // Passes
237276// test.True(t, false) // Fails
238238-func True(tb testing.TB, v bool) {
277277+func True(tb testing.TB, got bool, options ...Option) {
239278 tb.Helper()
240240- if !v {
279279+ cfg := defaultConfig()
280280+ cfg.title = "Not True"
281281+282282+ for _, option := range options {
283283+ if err := option.apply(&cfg); err != nil {
284284+ tb.Fatalf("True: could not apply options: %v", err)
285285+ return
286286+ }
287287+ }
288288+289289+ if !got {
241290 fail := failure[bool]{
242242- got: v,
243243- want: true,
244244- title: "Not True",
245245- comment: getComment(),
291291+ got: got,
292292+ want: true,
293293+ cfg: cfg,
246294 }
247295 tb.Fatal(fail.String())
248296 }
249297}
250298251251-// False fails if v is true.
299299+// False fails if got is true.
252300//
253301// test.False(t, false) // Passes
254302// test.False(t, true) // Fails
255255-func False(tb testing.TB, v bool) {
303303+func False(tb testing.TB, got bool, options ...Option) {
256304 tb.Helper()
257257- if v {
305305+ cfg := defaultConfig()
306306+ cfg.title = "Not False"
307307+308308+ for _, option := range options {
309309+ if err := option.apply(&cfg); err != nil {
310310+ tb.Fatalf("False: could not apply options: %v", err)
311311+ return
312312+ }
313313+ }
314314+315315+ if got {
258316 fail := failure[bool]{
259259- got: v,
260260- want: false,
261261- title: "Not False",
262262- comment: getComment(),
317317+ got: got,
318318+ want: false,
319319+ cfg: cfg,
263320 }
264321 tb.Fatal(fail.String())
265322 }
266323}
267324268268-// Diff fails if got != want and provides a rich diff.
269269-func Diff(tb testing.TB, got, want any) {
270270- // TODO: Nicer output for diff, don't like the +got -want thing
325325+// Diff fails if the two strings got and want are not equal and provides a rich
326326+// unified diff of the two for easy comparison.
327327+func Diff(tb testing.TB, got, want string) {
271328 tb.Helper()
272272- if diff := cmp.Diff(want, got); diff != "" {
273273- tb.Fatalf("\nMismatch (-want, +got):\n%s\n", diff)
329329+330330+ if diff := diff.Diff("want", []byte(want), "got", []byte(got)); diff != nil {
331331+ tb.Fatalf("\nDiff\n----\n%s\n", prettyDiff(string(diff)))
274332 }
275333}
276334277277-// DeepEqual fails if reflect.DeepEqual(got, want) == false.
278278-func DeepEqual(tb testing.TB, got, want any) {
335335+// DiffBytes fails if the two []byte got and want are not equal and provides a rich
336336+// unified diff of the two for easy comparison.
337337+func DiffBytes(tb testing.TB, got, want []byte) {
279338 tb.Helper()
280280- if !reflect.DeepEqual(got, want) {
281281- fail := failure[any]{
282282- got: got,
283283- want: want,
284284- title: "Not Equal",
285285- reason: "reflect.DeepEqual(got, want) returned false",
286286- comment: getComment(),
287287- }
288288- tb.Fatal(fail.String())
339339+340340+ if diff := diff.Diff("want", want, "got", got); diff != nil {
341341+ tb.Fatalf("\nDiff\n----\n%s\n", prettyDiff(string(diff)))
289342 }
290343}
291344292292-// Data returns the filepath to the testdata directory for the current package.
293293-//
294294-// When running tests, Go will change the cwd to the directory of the package under test. This means
295295-// that reference data stored in $CWD/testdata can be easily retrieved in the same way for any package.
296296-//
297297-// The $CWD/testdata directory is a Go idiom, common practice, and is completely ignored by the go tool.
298298-//
299299-// Data makes no guarantee that $CWD/testdata exists, it simply returns it's path.
300300-//
301301-// file := filepath.Join(test.Data(t), "test.txt")
302302-func Data(tb testing.TB) string {
303303- tb.Helper()
304304- cwd, err := os.Getwd()
305305- if err != nil {
306306- tb.Fatalf("could not get $CWD: %v", err)
307307- }
308308-309309- return filepath.Join(cwd, "testdata")
310310-}
311311-312312-// File fails if got does not match the contents of the given file.
313313-//
314314-// It takes a string and the path of a file to compare, use [Data] to obtain
315315-// the path to the current packages testdata directory.
316316-//
317317-// If the contents differ, the test will fail with output similar to executing git diff
318318-// on the contents.
319319-//
320320-// Files with differing line endings (e.g windows CR LF \r\n vs unix LF \n) will be normalised to
321321-// \n prior to comparison so this function will behave identically across multiple platforms.
322322-//
323323-// test.File(t, "hello\n", "expected.txt")
324324-func File(tb testing.TB, got, file string) {
325325- tb.Helper()
326326- f, err := filepath.Abs(file)
327327- if err != nil {
328328- tb.Fatalf("could not make %s absolute: %v", file, err)
329329- }
330330- contents, err := os.ReadFile(f)
331331- if err != nil {
332332- tb.Fatalf("could not read %s: %v", f, err)
333333- }
334334-335335- contents = bytes.ReplaceAll(contents, []byte("\r\n"), []byte("\n"))
336336-337337- if diff := diff.Diff(f, contents, "got", []byte(got)); diff != nil {
338338- tb.Fatalf("\nMismatch\n--------\n%s\n", prettyDiff(string(diff)))
339339- }
340340-}
341341-342342-// CaptureOutput captures and returns data printed to stdout and stderr by the provided function fn, allowing
345345+// CaptureOutput captures and returns data printed to [os.Stdout] and [os.Stderr] by the provided function fn, allowing
343346// you to test functions that write to those streams and do not have an option to pass in an [io.Writer].
344347//
345348// If the provided function returns a non nil error, the test is failed with the error logged as the reason.
···427430 wg.Wait()
428431429432 return capturedStdout, capturedStderr
430430-}
431431-432432-// getComment loads a Go line comment from a line where a test function has been called.
433433-//
434434-// If any error happens or there is no comment, an empty string is returned so as not
435435-// to influence the test with an unrelated error.
436436-func getComment() string {
437437- skip := 2 // Skip 2 frames, one for this function, the other for the calling test function
438438- _, file, line, ok := runtime.Caller(skip)
439439- if !ok {
440440- return ""
441441- }
442442-443443- f, err := os.Open(file)
444444- if err != nil {
445445- return ""
446446- }
447447- defer f.Close()
448448-449449- currentLine := 1 // Line numbers in source files start from 1
450450- scanner := bufio.NewScanner(f)
451451- for scanner.Scan() {
452452- // Skip through until we get to the line returned from runtime.Caller
453453- if currentLine != line {
454454- currentLine++
455455- continue
456456- }
457457-458458- _, comment, ok := strings.Cut(scanner.Text(), "//")
459459- if !ok {
460460- // There was no comment on this line
461461- return ""
462462- }
463463-464464- // Now comment will be everything from the "//" until the end of the line
465465- return strings.TrimSpace(comment)
466466- }
467467-468468- // Didn't find one
469469- return ""
470433}
471434472435// prettyDiff takes a string diff in unified diff format and colourises it for easier viewing.
+300-382
test_test.go
···33import (
44 "bytes"
55 "errors"
66+ "flag"
67 "fmt"
78 "io"
89 "os"
99- "path/filepath"
1010+ "slices"
1011 "testing"
11121313+ "github.com/FollowTheProcess/snapshot"
1214 "github.com/FollowTheProcess/test"
1313- "github.com/google/go-cmp/cmp"
1415)
1616+1717+var update = flag.Bool("update", false, "Update snapshots")
15181619// TB is a fake implementation of [testing.TB] that simply records in internal
1720// state whether or not it would have failed and what it would have written.
···3336 fmt.Fprintf(t.out, format, args...)
3437}
35383636-func TestPassFail(t *testing.T) {
3939+func TestTest(t *testing.T) {
3740 tests := []struct {
3838- testFunc func(tb testing.TB) // The test function we're... testing
3939- wantOut string // What we wanted the TB to print
4141+ fn func(tb testing.TB) // The test function we're... testing?
4042 name string // Name of the test case
4141- wantFail bool // Whether we wanted the testFunc to fail it's TB
4343+ wantFail bool // Whether it should fail
4244 }{
4345 {
4444- name: "equal string pass",
4545- testFunc: func(tb testing.TB) {
4646- tb.Helper()
4747- test.Equal(tb, "apples", "apples") // These obviously are equal
4646+ name: "Equal/pass",
4747+ fn: func(tb testing.TB) {
4848+ test.Equal(tb, "apples", "apples")
4849 },
4949- wantFail: false, // Should pass
5050- wantOut: "", // And write no output
5050+ wantFail: false,
5151 },
5252 {
5353- name: "equal string fail",
5454- testFunc: func(tb testing.TB) {
5555- tb.Helper()
5353+ name: "Equal/fail",
5454+ fn: func(tb testing.TB) {
5655 test.Equal(tb, "apples", "oranges")
5756 },
5857 wantFail: true,
5959- wantOut: "\nNot Equal\n---------\nGot:\tapples\nWanted:\toranges\n",
6058 },
6159 {
6262- name: "equal string fail with comment",
6363- testFunc: func(tb testing.TB) {
6464- tb.Helper()
6565- test.Equal(tb, "apples", "oranges") // apples are not oranges
6060+ name: "Equal/fail with context",
6161+ fn: func(tb testing.TB) {
6262+ test.Equal(tb, "apples", "oranges", test.Context("Apples are not oranges!"))
6663 },
6764 wantFail: true,
6868- wantOut: "\nNot Equal // apples are not oranges\n---------\nGot:\tapples\nWanted:\toranges\n",
6965 },
7066 {
7171- name: "equal int pass",
7272- testFunc: func(tb testing.TB) {
7373- tb.Helper()
7474- test.Equal(tb, 1, 1)
6767+ name: "Equal/fail context format",
6868+ fn: func(tb testing.TB) {
6969+ test.Equal(tb, "apples", "oranges", test.Context("Apples == Oranges: %v", false))
7070+ },
7171+ wantFail: true,
7272+ },
7373+ {
7474+ name: "Equal/fail with title",
7575+ fn: func(tb testing.TB) {
7676+ test.Equal(tb, "apples", "oranges", test.Title("My fruit test"))
7777+ },
7878+ wantFail: true,
7979+ },
8080+ {
8181+ name: "NotEqual/pass",
8282+ fn: func(tb testing.TB) {
8383+ test.NotEqual(tb, "apples", "oranges")
7584 },
7685 wantFail: false,
7777- wantOut: "",
7886 },
7987 {
8080- name: "equal int fail",
8181- testFunc: func(tb testing.TB) {
8282- tb.Helper()
8383- test.Equal(tb, 1, 42)
8484- },
8585- wantFail: true,
8686- wantOut: "\nNot Equal\n---------\nGot:\t1\nWanted:\t42\n",
8787- },
8888- {
8989- name: "nearly equal pass",
9090- testFunc: func(tb testing.TB) {
9191- tb.Helper()
9292- test.NearlyEqual(tb, 3.0000000001, 3.0)
9393- },
9494- wantFail: false,
9595- wantOut: "",
9696- },
9797- {
9898- name: "nearly equal fail",
9999- testFunc: func(tb testing.TB) {
100100- tb.Helper()
101101- test.NearlyEqual(tb, 3.0000001, 3.0)
102102- },
103103- wantFail: true,
104104- wantOut: "\nNot NearlyEqual\n---------------\nGot:\t3.0000001\nWanted:\t3\n\nDifference 9.999999983634211e-08 exceeds maximum tolerance of 1e-08\n",
105105- },
106106- {
107107- name: "nearly equal fail with comment",
108108- testFunc: func(tb testing.TB) {
109109- tb.Helper()
110110- test.NearlyEqual(tb, 3.0000001, 3.0) // Ooof so close
111111- },
112112- wantFail: true,
113113- wantOut: "\nNot NearlyEqual // Ooof so close\n---------------\nGot:\t3.0000001\nWanted:\t3\n\nDifference 9.999999983634211e-08 exceeds maximum tolerance of 1e-08\n",
114114- },
115115- {
116116- name: "not equal string pass",
117117- testFunc: func(tb testing.TB) {
118118- tb.Helper()
119119- test.NotEqual(tb, "apples", "oranges") // Should pass, these aren't equal
120120- },
121121- wantFail: false,
122122- wantOut: "",
123123- },
124124- {
125125- name: "not equal string fail",
126126- testFunc: func(tb testing.TB) {
127127- tb.Helper()
8888+ name: "NotEqual/fail",
8989+ fn: func(tb testing.TB) {
12890 test.NotEqual(tb, "apples", "apples")
12991 },
13092 wantFail: true,
131131- wantOut: "\nEqual\n-----\nGot:\tapples\n\nExpected values to be different\n",
13293 },
13394 {
134134- name: "not equal string fail with comment",
135135- testFunc: func(tb testing.TB) {
136136- tb.Helper()
137137- test.NotEqual(tb, "apples", "apples") // different apples
9595+ name: "NotEqual/fail with context",
9696+ fn: func(tb testing.TB) {
9797+ test.NotEqual(tb, 42, 42, test.Context("42 is the meaning of life"))
13898 },
13999 wantFail: true,
140140- wantOut: "\nEqual // different apples\n-----\nGot:\tapples\n\nExpected values to be different\n",
141100 },
142101 {
143143- name: "not equal int pass",
144144- testFunc: func(tb testing.TB) {
145145- tb.Helper()
146146- test.NotEqual(tb, 1, 42)
102102+ name: "NotEqual/fail context format",
103103+ fn: func(tb testing.TB) {
104104+ test.NotEqual(tb, 42, 42, test.Context("42 == meaning of life: %v", true))
105105+ },
106106+ wantFail: true,
107107+ },
108108+ {
109109+ name: "NotEqual/fail with title",
110110+ fn: func(tb testing.TB) {
111111+ test.NotEqual(tb, "apples", "apples", test.Title("My fruit test"))
112112+ },
113113+ wantFail: true,
114114+ },
115115+ {
116116+ name: "EqualFunc/pass",
117117+ fn: func(tb testing.TB) {
118118+ test.EqualFunc(tb, []int{1, 2, 3, 4}, []int{1, 2, 3, 4}, slices.Equal)
147119 },
148120 wantFail: false,
149149- wantOut: "",
150121 },
151122 {
152152- name: "not equal int fail",
153153- testFunc: func(tb testing.TB) {
154154- tb.Helper()
155155- test.NotEqual(tb, 1, 1)
123123+ name: "EqualFunc/fail",
124124+ fn: func(tb testing.TB) {
125125+ cmp := func(a, b []string) bool { return false } // Cheating
126126+ test.EqualFunc(tb, []string{"hello"}, []string{"there"}, cmp)
156127 },
157128 wantFail: true,
158158- wantOut: "\nEqual\n-----\nGot:\t1\n\nExpected values to be different\n",
159129 },
160130 {
161161- name: "not equal int fail with comment",
162162- testFunc: func(tb testing.TB) {
163163- tb.Helper()
164164- test.NotEqual(tb, 1, 1) // 1 != 1?
131131+ name: "EqualFunc/fail with context",
132132+ fn: func(tb testing.TB) {
133133+ test.EqualFunc(
134134+ tb,
135135+ []string{"hello"},
136136+ []string{"there"},
137137+ slices.Equal,
138138+ test.Context("some context here"),
139139+ )
165140 },
166141 wantFail: true,
167167- wantOut: "\nEqual // 1 != 1?\n-----\nGot:\t1\n\nExpected values to be different\n",
168142 },
169143 {
170170- name: "ok pass",
171171- testFunc: func(tb testing.TB) {
172172- tb.Helper()
144144+ name: "EqualFunc/fail context format",
145145+ fn: func(tb testing.TB) {
146146+ test.EqualFunc(
147147+ tb,
148148+ []string{"hello"},
149149+ []string{"there"},
150150+ slices.Equal,
151151+ test.Context("who's bad at testing... %s", "you"),
152152+ )
153153+ },
154154+ wantFail: true,
155155+ },
156156+ {
157157+ name: "EqualFunc/fail with title",
158158+ fn: func(tb testing.TB) {
159159+ test.EqualFunc(tb, []string{"hello"}, []string{"there"}, slices.Equal, test.Title("Hello!"))
160160+ },
161161+ wantFail: true,
162162+ },
163163+ {
164164+ name: "NotEqualFunc/pass",
165165+ fn: func(tb testing.TB) {
166166+ test.NotEqualFunc(tb, []int{1, 2, 3, 4}, []int{5, 6, 7, 8}, slices.Equal)
167167+ },
168168+ wantFail: false,
169169+ },
170170+ {
171171+ name: "NotEqualFunc/fail",
172172+ fn: func(tb testing.TB) {
173173+ cmp := func(a, b []string) bool { return true } // Cheating
174174+ test.NotEqualFunc(tb, []string{"hello"}, []string{"there"}, cmp)
175175+ },
176176+ wantFail: true,
177177+ },
178178+ {
179179+ name: "NotEqualFunc/fail with context",
180180+ fn: func(tb testing.TB) {
181181+ test.NotEqualFunc(
182182+ tb,
183183+ []string{"hello"},
184184+ []string{"hello"},
185185+ slices.Equal,
186186+ test.Context("some context here"),
187187+ )
188188+ },
189189+ wantFail: true,
190190+ },
191191+ {
192192+ name: "NotEqualFunc/fail context format",
193193+ fn: func(tb testing.TB) {
194194+ test.NotEqualFunc(
195195+ tb,
196196+ []string{"hello"},
197197+ []string{"hello"},
198198+ slices.Equal,
199199+ test.Context("who's bad at testing... %s", "you"),
200200+ )
201201+ },
202202+ wantFail: true,
203203+ },
204204+ {
205205+ name: "NotEqualFunc/fail with title",
206206+ fn: func(tb testing.TB) {
207207+ test.NotEqualFunc(tb, []string{"hello"}, []string{"hello"}, slices.Equal, test.Title("Hello!"))
208208+ },
209209+ wantFail: true,
210210+ },
211211+ {
212212+ name: "NearlyEqual/pass",
213213+ fn: func(tb testing.TB) {
214214+ test.NearlyEqual(tb, 3.0000000001, 3.0)
215215+ },
216216+ wantFail: false,
217217+ },
218218+ {
219219+ name: "NearlyEqual/fail",
220220+ fn: func(tb testing.TB) {
221221+ test.NearlyEqual(tb, 3.0000001, 3.0)
222222+ },
223223+ wantFail: true,
224224+ },
225225+ {
226226+ name: "NearlyEqual/fail custom tolerance",
227227+ fn: func(tb testing.TB) {
228228+ test.NearlyEqual(tb, 3.2, 3.0, test.FloatEqualityThreshold(0.1))
229229+ },
230230+ wantFail: true,
231231+ },
232232+ {
233233+ name: "NearlyEqual/fail with context",
234234+ fn: func(tb testing.TB) {
235235+ test.NearlyEqual(tb, 3.0000001, 3.0, test.Context("Numbers don't work that way"))
236236+ },
237237+ wantFail: true,
238238+ },
239239+ {
240240+ name: "Ok/pass",
241241+ fn: func(tb testing.TB) {
173242 test.Ok(tb, nil)
174243 },
175244 wantFail: false,
176176- wantOut: "",
177245 },
178246 {
179179- name: "ok fail",
180180- testFunc: func(tb testing.TB) {
181181- tb.Helper()
247247+ name: "Ok/fail",
248248+ fn: func(tb testing.TB) {
182249 test.Ok(tb, errors.New("uh oh"))
183250 },
184251 wantFail: true,
185185- wantOut: "\nNot Ok\n------\nGot:\tuh oh\nWanted:\t<nil>\n",
186252 },
187253 {
188188- name: "ok fail with comment",
189189- testFunc: func(tb testing.TB) {
190190- tb.Helper()
191191- test.Ok(tb, errors.New("uh oh")) // Calling some function
254254+ name: "Ok/fail with context",
255255+ fn: func(tb testing.TB) {
256256+ test.Ok(tb, errors.New("uh oh"), test.Context("Could not frobnicate the baz"))
192257 },
193258 wantFail: true,
194194- wantOut: "\nNot Ok // Calling some function\n------\nGot:\tuh oh\nWanted:\t<nil>\n",
195259 },
196260 {
197197- name: "err pass",
198198- testFunc: func(tb testing.TB) {
199199- tb.Helper()
200200- test.Err(tb, errors.New("uh oh"))
261261+ name: "Ok/fail with title",
262262+ fn: func(tb testing.TB) {
263263+ test.Ok(tb, errors.New("uh oh"), test.Title("Bang!"))
264264+ },
265265+ wantFail: true,
266266+ },
267267+ {
268268+ name: "Err/pass",
269269+ fn: func(tb testing.TB) {
270270+ test.Err(tb, errors.New("bang!"))
201271 },
202272 wantFail: false,
203203- wantOut: "",
204273 },
205274 {
206206- name: "err fail",
207207- testFunc: func(tb testing.TB) {
208208- tb.Helper()
275275+ name: "Err/fail",
276276+ fn: func(tb testing.TB) {
209277 test.Err(tb, nil)
210278 },
211279 wantFail: true,
212212- wantOut: "\nNot Err\n-------\nGot:\t<nil>\nWanted:\terror\n",
213280 },
214281 {
215215- name: "err fail with comment",
216216- testFunc: func(tb testing.TB) {
217217- tb.Helper()
218218- test.Err(tb, nil) // Should have failed
282282+ name: "Err/fail with context",
283283+ fn: func(tb testing.TB) {
284284+ test.Err(tb, nil, test.Context("Frobnicated the baz when it should have failed"))
219285 },
220286 wantFail: true,
221221- wantOut: "\nNot Err // Should have failed\n-------\nGot:\t<nil>\nWanted:\terror\n",
222287 },
223288 {
224224- name: "true pass",
225225- testFunc: func(tb testing.TB) {
226226- tb.Helper()
289289+ name: "Err/fail with title",
290290+ fn: func(tb testing.TB) {
291291+ test.Err(tb, nil, test.Title("Everything is fine?"))
292292+ },
293293+ wantFail: true,
294294+ },
295295+ {
296296+ name: "WantErr/pass error",
297297+ fn: func(tb testing.TB) {
298298+ test.WantErr(tb, errors.New("bang"), true) // Wanted an error and got one - should pass
299299+ },
300300+ wantFail: false,
301301+ },
302302+ {
303303+ name: "WantErr/pass nil",
304304+ fn: func(tb testing.TB) {
305305+ test.WantErr(tb, nil, false) // Didn't want an error and got nil - should pass
306306+ },
307307+ wantFail: false,
308308+ },
309309+ {
310310+ name: "WantErr/fail error",
311311+ fn: func(tb testing.TB) {
312312+ test.WantErr(tb, errors.New("bang"), false) // Got an error but didn't want one - should fail
313313+ },
314314+ wantFail: true,
315315+ },
316316+ {
317317+ name: "WantErr/fail nil",
318318+ fn: func(tb testing.TB) {
319319+ test.WantErr(tb, nil, true) // Didn't get an error but wanted one - should fail
320320+ },
321321+ wantFail: true,
322322+ },
323323+ {
324324+ name: "WantErr/fail with context",
325325+ fn: func(tb testing.TB) {
326326+ test.WantErr(tb, errors.New("bang"), false, test.Context("Errors are bad!"))
327327+ },
328328+ wantFail: true,
329329+ },
330330+ {
331331+ name: "WantErr/fail with title",
332332+ fn: func(tb testing.TB) {
333333+ test.WantErr(tb, errors.New("bang"), false, test.Title("A very bad test"))
334334+ },
335335+ wantFail: true,
336336+ },
337337+ {
338338+ name: "True/pass",
339339+ fn: func(tb testing.TB) {
227340 test.True(tb, true)
228341 },
229342 wantFail: false,
230230- wantOut: "",
231343 },
232344 {
233233- name: "true fail",
234234- testFunc: func(tb testing.TB) {
235235- tb.Helper()
345345+ name: "True/fail",
346346+ fn: func(tb testing.TB) {
236347 test.True(tb, false)
237348 },
238349 wantFail: true,
239239- wantOut: "\nNot True\n--------\nGot:\tfalse\nWanted:\ttrue\n",
240350 },
241351 {
242242- name: "true fail with comment",
243243- testFunc: func(tb testing.TB) {
244244- tb.Helper()
245245- test.True(tb, false) // Comment here
352352+ name: "True/fail with context",
353353+ fn: func(tb testing.TB) {
354354+ test.True(tb, false, test.Context("must always be true"))
246355 },
247356 wantFail: true,
248248- wantOut: "\nNot True // Comment here\n--------\nGot:\tfalse\nWanted:\ttrue\n",
249357 },
250358 {
251251- name: "false pass",
252252- testFunc: func(tb testing.TB) {
253253- tb.Helper()
359359+ name: "True/fail with title",
360360+ fn: func(tb testing.TB) {
361361+ test.True(tb, false, test.Title("Argh!"))
362362+ },
363363+ wantFail: true,
364364+ },
365365+ {
366366+ name: "False/pass",
367367+ fn: func(tb testing.TB) {
254368 test.False(tb, false)
255369 },
256370 wantFail: false,
257257- wantOut: "",
258371 },
259372 {
260260- name: "false fail",
261261- testFunc: func(tb testing.TB) {
262262- tb.Helper()
373373+ name: "False/fail",
374374+ fn: func(tb testing.TB) {
263375 test.False(tb, true)
264376 },
265377 wantFail: true,
266266- wantOut: "\nNot False\n---------\nGot:\ttrue\nWanted:\tfalse\n",
267378 },
268379 {
269269- name: "false fail with comment",
270270- testFunc: func(tb testing.TB) {
271271- tb.Helper()
272272- test.False(tb, true) // Should always be false
380380+ name: "False/fail with context",
381381+ fn: func(tb testing.TB) {
382382+ test.False(tb, true, test.Context("must always be false"))
273383 },
274384 wantFail: true,
275275- wantOut: "\nNot False // Should always be false\n---------\nGot:\ttrue\nWanted:\tfalse\n",
276385 },
277386 {
278278- name: "equal func pass",
279279- testFunc: func(tb testing.TB) {
280280- tb.Helper()
281281- rubbishEqual := func(a, b string) bool {
282282- return true // Always equal
283283- }
284284- test.EqualFunc(tb, "word", "different word", rubbishEqual)
285285- },
286286- wantFail: false,
287287- wantOut: "",
288288- },
289289- {
290290- name: "equal func fail",
291291- testFunc: func(tb testing.TB) {
292292- tb.Helper()
293293- rubbishEqual := func(a, b string) bool {
294294- return false // Never equal
295295- }
296296- test.EqualFunc(tb, "word", "word", rubbishEqual)
387387+ name: "False/fail with title",
388388+ fn: func(tb testing.TB) {
389389+ test.False(tb, true, test.Title("Argh!"))
297390 },
298391 wantFail: true,
299299- wantOut: "\nNot Equal\n---------\nGot:\tword\nWanted:\tword\n\nequal(got, want) returned false\n",
300392 },
301393 {
302302- name: "equal func fail with comment",
303303- testFunc: func(tb testing.TB) {
304304- tb.Helper()
305305- rubbishEqual := func(a, b string) bool {
306306- return false // Never equal
307307- }
308308- test.EqualFunc(tb, "word", "word", rubbishEqual) // Uh oh
309309- },
310310- wantFail: true,
311311- wantOut: "\nNot Equal // Uh oh\n---------\nGot:\tword\nWanted:\tword\n\nequal(got, want) returned false\n",
312312- },
313313- {
314314- name: "not equal func pass",
315315- testFunc: func(tb testing.TB) {
316316- tb.Helper()
317317- rubbishNotEqual := func(a, b string) bool {
318318- return false // Never equal
319319- }
320320- test.NotEqualFunc(tb, "word", "word", rubbishNotEqual)
321321- },
322322- wantFail: false,
323323- wantOut: "",
324324- },
325325- {
326326- name: "not equal func fail",
327327- testFunc: func(tb testing.TB) {
328328- tb.Helper()
329329- rubbishNotEqual := func(a, b string) bool {
330330- return true // Always equal
331331- }
332332- test.NotEqualFunc(tb, "word", "different word", rubbishNotEqual)
333333- },
334334- wantFail: true,
335335- wantOut: "\nEqual\n-----\nGot:\tword\n\nequal(got, want) returned true\n",
336336- },
337337- {
338338- name: "not equal func fail with comment",
339339- testFunc: func(tb testing.TB) {
340340- tb.Helper()
341341- rubbishNotEqual := func(a, b string) bool {
342342- return true // Always equal
343343- }
344344- test.NotEqualFunc(tb, "word", "different word", rubbishNotEqual) // Bad equal
345345- },
346346- wantFail: true,
347347- wantOut: "\nEqual // Bad equal\n-----\nGot:\tword\n\nequal(got, want) returned true\n",
348348- },
349349- {
350350- name: "deep equal pass",
351351- testFunc: func(tb testing.TB) {
352352- tb.Helper()
353353- a := []string{"a", "b", "c"}
354354- b := []string{"a", "b", "c"}
394394+ name: "Diff/pass",
395395+ fn: func(tb testing.TB) {
396396+ got := "Some\nstuff here in this file\nlines as well wow\nsome more stuff\n"
397397+ want := "Some\nstuff here in this file\nlines as well wow\nsome more stuff\n"
355398356356- test.DeepEqual(tb, a, b)
357357- },
358358- wantFail: false,
359359- wantOut: "",
360360- },
361361- {
362362- name: "deep equal fail",
363363- testFunc: func(tb testing.TB) {
364364- tb.Helper()
365365- a := []string{"a", "b", "c"}
366366- b := []string{"d", "e", "f"}
367367-368368- test.DeepEqual(tb, a, b)
369369- },
370370- wantFail: true,
371371- wantOut: "\nNot Equal\n---------\nGot:\t[a b c]\nWanted:\t[d e f]\n\nreflect.DeepEqual(got, want) returned false\n",
372372- },
373373- {
374374- name: "deep equal fail with comment",
375375- testFunc: func(tb testing.TB) {
376376- tb.Helper()
377377- a := []string{"a", "b", "c"}
378378- b := []string{"d", "e", "f"}
379379-380380- test.DeepEqual(tb, a, b) // Oh no!
381381- },
382382- wantFail: true,
383383- wantOut: "\nNot Equal // Oh no!\n---------\nGot:\t[a b c]\nWanted:\t[d e f]\n\nreflect.DeepEqual(got, want) returned false\n",
384384- },
385385- {
386386- name: "want err pass when got and wanted",
387387- testFunc: func(tb testing.TB) {
388388- tb.Helper()
389389- test.WantErr(tb, errors.New("uh oh"), true) // We wanted an error and got one
390390- },
391391- wantFail: false,
392392- wantOut: "",
393393- },
394394- {
395395- name: "want err fail when got and not wanted",
396396- testFunc: func(tb testing.TB) {
397397- tb.Helper()
398398- test.WantErr(tb, errors.New("uh oh"), false)
399399- },
400400- wantFail: true,
401401- wantOut: "\nWantErr\n-------\nGot:\tuh oh\nWanted:\t<nil>\n\nGot an unexpected error: uh oh\n",
402402- },
403403- {
404404- name: "want err fail when got and not wanted with comment",
405405- testFunc: func(tb testing.TB) {
406406- tb.Helper()
407407- test.WantErr(tb, errors.New("uh oh"), false) // comment
408408- },
409409- wantFail: true,
410410- wantOut: "\nWantErr // comment\n-------\nGot:\tuh oh\nWanted:\t<nil>\n\nGot an unexpected error: uh oh\n",
411411- },
412412- {
413413- name: "want err pass when not got and not wanted",
414414- testFunc: func(tb testing.TB) {
415415- tb.Helper()
416416- test.WantErr(tb, nil, false) // Didn't want an error and didn't get one
417417- },
418418- wantFail: false,
419419- wantOut: "",
420420- },
421421- {
422422- name: "want err fail when not got but wanted",
423423- testFunc: func(tb testing.TB) {
424424- tb.Helper()
425425- test.WantErr(tb, nil, true)
426426- },
427427- wantFail: true,
428428- wantOut: "\nWantErr\n-------\nGot:\t<nil>\nWanted:\terror\n\nWanted an error but got <nil>\n",
429429- },
430430- {
431431- name: "want err fail when not got but wanted with comment",
432432- testFunc: func(tb testing.TB) {
433433- tb.Helper()
434434- test.WantErr(tb, nil, true) // comment
435435- },
436436- wantFail: true,
437437- wantOut: "\nWantErr // comment\n-------\nGot:\t<nil>\nWanted:\terror\n\nWanted an error but got <nil>\n",
438438- },
439439- {
440440- name: "file pass",
441441- testFunc: func(tb testing.TB) {
442442- tb.Helper()
443443- test.File(tb, "hello\n", filepath.Join(test.Data(t), "file.txt"))
444444- },
445445- wantFail: false,
446446- wantOut: "",
447447- },
448448- {
449449- name: "diff pass string",
450450- testFunc: func(tb testing.TB) {
451451- tb.Helper()
452452- test.Diff(tb, "hello", "hello")
453453- },
454454- wantFail: false,
455455- wantOut: "",
456456- },
457457- {
458458- name: "diff fail string",
459459- testFunc: func(tb testing.TB) {
460460- tb.Helper()
461461- test.Diff(tb, "hello", "hello there")
462462- },
463463- wantFail: true,
464464- wantOut: fmt.Sprintf(
465465- "\nMismatch (-want, +got):\n%s\n",
466466- cmp.Diff("hello there", "hello"),
467467- ), // Output equivalent to diff
468468- },
469469- {
470470- name: "diff pass string slice",
471471- testFunc: func(tb testing.TB) {
472472- tb.Helper()
473473- got := []string{"hello", "there"}
474474- want := []string{"hello", "there"}
475399 test.Diff(tb, got, want)
476400 },
477401 wantFail: false,
478478- wantOut: "",
479402 },
480403 {
481481- name: "diff fail string slice",
482482- testFunc: func(tb testing.TB) {
483483- tb.Helper()
484484- got := []string{"hello", "there"}
485485- want := []string{"not", "me"}
404404+ name: "Diff/fail",
405405+ fn: func(tb testing.TB) {
406406+ got := "Some\nstuff here in this file\nlines as well wow\nsome more stuff\n"
407407+ want := "Some\ndifferent stuff here in this file\nthis line is different\nsome more stuff\n"
486408 test.Diff(tb, got, want)
487409 },
488410 wantFail: true,
489489- wantOut: fmt.Sprintf(
490490- "\nMismatch (-want, +got):\n%s\n",
491491- cmp.Diff([]string{"not", "me"}, []string{"hello", "there"}),
492492- ), // Output equivalent to diff
411411+ },
412412+ {
413413+ name: "DiffBytes/pass",
414414+ fn: func(tb testing.TB) {
415415+ got := []byte("Some\nstuff here in this file\nlines as well wow\nsome more stuff\n")
416416+ want := []byte("Some\nstuff here in this file\nlines as well wow\nsome more stuff\n")
417417+418418+ test.DiffBytes(tb, got, want)
419419+ },
420420+ wantFail: false,
421421+ },
422422+ {
423423+ name: "DiffBytes/fail",
424424+ fn: func(tb testing.TB) {
425425+ got := []byte("Some\nstuff here in this file\nlines as well wow\nsome more stuff\n")
426426+ want := []byte("Some\ndifferent stuff here in this file\nthis line is different\nsome more stuff\n")
427427+ test.DiffBytes(tb, got, want)
428428+ },
429429+ wantFail: true,
493430 },
494431 }
495432···497434 t.Run(tt.name, func(t *testing.T) {
498435 buf := &bytes.Buffer{}
499436 tb := &TB{out: buf}
437437+ snap := snapshot.New(t, snapshot.Update(*update))
500438501439 if tb.failed {
502440 t.Fatalf("%s initial failed state should be false", tt.name)
503441 }
504442505505- // Call the test function, passing in our mock TB that simply
506506- // records whether or not it would have failed and what it would
507507- // have written
508508- tt.testFunc(tb)
443443+ // Call the test function, passing in the mock TB that just records
444444+ // what a "real" TB would have done
445445+ tt.fn(tb)
509446510447 if tb.failed != tt.wantFail {
511511- t.Fatalf(
512512- "\n%s failure mismatch\n--------------\nfailed:\t%v\nwanted failure:\t%v\n",
513513- tt.name,
514514- tb.failed,
515515- tt.wantFail,
516516- )
448448+ t.Fatalf("\nIncorrect Failure\n\ntb.failed:\t%v\nwanted:\t%v\n", tb.failed, tt.wantFail)
517449 }
518450519519- if got := buf.String(); got != tt.wantOut {
520520- t.Errorf(
521521- "\n%s output mismatch\n---------------\nGot:\t%s\nWanted:\t%s\n",
522522- tt.name,
523523- got,
524524- tt.wantOut,
525525- )
451451+ // Test the output matches our snapshot file, only for failed tests
452452+ // as there should be no output for passed tests
453453+ if !tb.failed {
454454+ if buf.Len() != 0 {
455455+ t.Fatalf("\nIncorrect Output\n\nA passed test should have no output, got: %s\n", buf.String())
456456+ }
457457+ } else {
458458+ snap.Snap(buf.String())
526459 }
527460 })
528528- }
529529-}
530530-531531-func TestData(t *testing.T) {
532532- got := test.Data(t)
533533-534534- cwd, err := os.Getwd()
535535- if err != nil {
536536- t.Fatalf("Test for Data could not get cwd: %v", err)
537537- }
538538-539539- want := filepath.Join(cwd, "testdata")
540540-541541- if got != want {
542542- t.Errorf("\nGot:\t%s\nWanted:\t%s\n", got, want)
543461 }
544462}
545463
···11+22+Diff
33+----
44+diff want got
55+--- want
66++++ got
77+@@ -1,4 +1,4 @@
88+ Some
99+- different stuff here in this file
1010+- this line is different
1111++ stuff here in this file
1212++ lines as well wow
1313+ some more stuff
1414+
···11+22+Diff
33+----
44+diff want got
55+--- want
66++++ got
77+@@ -1,4 +1,4 @@
88+ Some
99+- different stuff here in this file
1010+- this line is different
1111++ stuff here in this file
1212++ lines as well wow
1313+ some more stuff
1414+
+6
testdata/snapshots/TestTest/Equal/fail.snap.txt
···11+22+Not Equal
33+---------
44+55+Got: apples
66+Wanted: oranges