From e28e81919d87ca6a571cb05178c0e789d5244c0d Mon Sep 17 00:00:00 2001 From: Tom Fleet Date: Tue, 15 Aug 2023 20:50:19 +0100 Subject: [PATCH] Add test.ErrIsWanted for table driven tests (#4) --- README.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++ test.go | 16 ++++++++++-- test_test.go | 4 +++ 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a5228d..a6a7973 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,77 @@ Will give you: } ``` +### Table Driven Tests + +Table driven tests are great! But when you test errors too it can get a bit awkward, you have to do the `if (err != nil) != tt.wantErr` thing and I personally +*always* have to do the boolean logic in my head to make sure I got that right. Enter `test.ErrIsWanted`: + +```go +func TestTableThings(t *testing.T) { + tests := []struct { + name string + want int + wantErr bool + }{ + { + name: "no error", + want: 4, + wantErr: false, + }, + { + name: "yes error", + want: 4, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := SomeFunction() + + test.ErrIsWanted(t, err, tt.wantErr) + test.Equal(t, got, tt.want) + }) + } +} +``` + +Which is basically semantically equivalent to: + +```go +func TestTableThings(t *testing.T) { + tests := []struct { + name string + want int + wantErr bool + }{ + { + name: "no error", + want: 4, + wantErr: false, + }, + { + name: "yes error", + want: 4, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := SomeFunction() + + if tt.wantErr { + test.Err(t, err) + } else { + test.Ok(t, err) + } + test.Equal(t, got, tt.want) + }) + } +} +``` + ### Credits This package was created with [copier] and the [FollowTheProcess/go_copier] project template. diff --git a/test.go b/test.go index 5d49d9e..a1d625f 100644 --- a/test.go +++ b/test.go @@ -63,11 +63,23 @@ func Err(t testing.TB, err error) { } } +// ErrIsWanted fails if you got an error and didn't want it, or if you +// didn't get an error but wanted one. +// +// It simplifies checking for errors in table driven tests where on any +// iteration err could either be nil or not. +func ErrIsWanted(t testing.TB, err error, want bool) { + t.Helper() + if (err != nil) != want { + t.Fatalf("\nGot error:\t%v\nWanted error: %v\n", err, want) + } +} + // True fails if v is false. func True(t testing.TB, v bool) { t.Helper() if !v { - t.Fatalf("\nGot:\t%+v\nWanted:\t%+v", v, true) + t.Fatalf("\nGot:\t%v\nWanted:\t%v", v, true) } } @@ -75,7 +87,7 @@ func True(t testing.TB, v bool) { func False(t testing.TB, v bool) { t.Helper() if v { - t.Fatalf("\nGot:\t%+v\nWanted:\t%+v", v, false) + t.Fatalf("\nGot:\t%v\nWanted:\t%v", v, false) } } diff --git a/test_test.go b/test_test.go index c7575fd..f0742b3 100644 --- a/test_test.go +++ b/test_test.go @@ -86,6 +86,8 @@ func TestPass(t *testing.T) { test.Diff(tb, struct{ Name string }{Name: "dave"}, struct{ Name string }{Name: "dave"}) }, func(tb testing.TB) { test.DeepEqual(tb, []string{"hello"}, []string{"hello"}) }, + func(tb testing.TB) { test.ErrIsWanted(tb, errors.New("uh oh"), true) }, + func(tb testing.TB) { test.ErrIsWanted(tb, nilErr(), false) }, } for _, fn := range passFns { @@ -155,6 +157,8 @@ func TestFail(t *testing.T) { test.Diff(tb, struct{ Name string }{Name: "dave"}, struct{ Name string }{Name: "john"}) }, func(tb testing.TB) { test.DeepEqual(tb, []string{"hello"}, []string{"world"}) }, + func(tb testing.TB) { test.ErrIsWanted(tb, errors.New("uh oh"), false) }, + func(tb testing.TB) { test.ErrIsWanted(tb, nilErr(), true) }, } for _, fn := range failFns {