From a9c1184d6bf8966c258feeba8eb81a5686a98b5f Mon Sep 17 00:00:00 2001 From: Aneurin Barker Snook Date: Thu, 18 Jul 2024 06:53:20 +0100 Subject: [PATCH] improve validate testing with examples also simplified structure of test cases and failure logging --- README.md | 26 +----- all_test.go | 47 ++++++----- chars_test.go | 73 ++++++++-------- email_test.go | 48 +++++++---- equal_test.go | 58 ++++--------- error_test.go | 56 ++++++------- in_test.go | 70 ++++++---------- length.go | 4 +- length_test.go | 71 ++++++++-------- number_test.go | 223 +++++++++++++++++++++++++------------------------ size.go | 4 +- size_test.go | 83 +++++++++++------- url_test.go | 39 +++++---- uuid_test.go | 43 +++++----- 14 files changed, 417 insertions(+), 428 deletions(-) diff --git a/README.md b/README.md index 5b2929b..d79e8e8 100644 --- a/README.md +++ b/README.md @@ -4,31 +4,7 @@ A suite of straightforward validation functions. You put something in, you get b ## Error handling -You can use `errors.Is()` to ascertain the type of errors thrown by validation functions. This may be helpful to control side effects, particularly if using multiple validators and returning early (similar to a strongly-typed try-catch). For example: - -## Example - -```go -package main - -import ( - "errors" - "fmt" - - "github.com/annybs/go/validate" -) - -func main() { - v := validate.Equal("a") - if err := v("b"); err != nil { - if errors.Is(err, validate.ErrNotEqual) { - fmt.Println("failed successfully") - } else { - fmt.Println("failed unsuccessfully") - } - } -} -``` +You can use `errors.Is()` to ascertain the type of errors returned by validation functions. This may be helpful to control side effects, particularly if you are using multiple validators or want to change the error message. ## License diff --git a/all_test.go b/all_test.go index 5edf0ae..f3a5b90 100644 --- a/all_test.go +++ b/all_test.go @@ -2,40 +2,43 @@ package validate import ( "errors" + "fmt" "testing" ) -func TestAll(t *testing.T) { - type TestCase[T any] struct { - Input T - F func(T) error - Err error - } +func ExampleAll() { + testAll := All(MinLength(4), Chars("0123456789abcdef")) + fmt.Println(testAll("invalid input")) + // Output: contains disallowed characters +} - f := All( +func TestAll(t *testing.T) { + testAll := All( MinLength(4), MaxLength(8), Chars("0123456789abcdef"), In("abcd", "abcdef", "12345678"), ) - testCases := []TestCase[string]{ - {Input: "abcd", F: f}, - {Input: "abcdef", F: f}, - {Input: "12345678", F: f}, - {Input: "abc", F: f, Err: ErrMustBeLonger}, - {Input: "abcdef012", F: f, Err: ErrMustBeShorter}, - {Input: "abcdefgh", F: f, Err: ErrDisallowedChars}, - {Input: "01abcd", F: f, Err: ErrValueNotAllowed}, + testCases := map[string]error{ + "abcd": nil, + "abcdef": nil, + "12345678": nil, + + "abc": ErrMustBeLonger.With(4), + "abcdef012": ErrMustBeShorter.With(8), + "abcdefgh": ErrDisallowedChars, + "01abcd": ErrValueNotAllowed, } - for n, tc := range testCases { - t.Logf("(%d) Testing %q", n, tc.Input) + for input, want := range testCases { + t.Run(input, func(t *testing.T) { + got := testAll(input) - err := tc.F(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) - } + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) } } diff --git a/chars_test.go b/chars_test.go index 415640e..704e10f 100644 --- a/chars_test.go +++ b/chars_test.go @@ -2,59 +2,60 @@ package validate import ( "errors" + "fmt" "testing" ) +func ExampleChars() { + testChars := Chars("0123456789abcdef") + fmt.Println(testChars("invalid input")) + // Output: contains disallowed characters +} + +func ExampleExceptChars() { + testExceptChars := Chars("0123456789abcdef") + fmt.Println(testExceptChars("invalid input")) + // Output: contains disallowed characters +} + func TestChars(t *testing.T) { - type TestCase struct { - Input string - C string - Err error + testCases := map[string]map[string]error{ + "0123456789abcdef": {"abcd1234": nil, "abcd 1234": ErrDisallowedChars, "ghijklmno": ErrDisallowedChars}, } - hexRange := "0123456789abcdef" + for setup, values := range testCases { + testChars := Chars(setup) - testCases := []TestCase{ - {Input: "abcd1234", C: hexRange}, - {Input: "abcd 1234", C: hexRange, Err: ErrDisallowedChars}, - {Input: "ghijklmno", C: hexRange, Err: ErrDisallowedChars}, - } + for input, want := range values { + t.Run(input, func(t *testing.T) { + got := testChars(input) - for n, tc := range testCases { - t.Logf("(%d) Testing %q against %q", n, tc.Input, tc.C) - - f := Chars(tc.C) - err := f(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) } } } func TestExceptChars(t *testing.T) { - type TestCase struct { - Input string - C string - Err error + testCases := map[string]map[string]error{ + "0123456789abcdef": {"abcd1234": ErrDisallowedChars, "abcd 1234": ErrDisallowedChars, "ghijklmno": nil}, } - hexRange := "0123456789abcdef" + for setup, values := range testCases { + testExceptChars := ExceptChars(setup) - testCases := []TestCase{ - {Input: "abcd1234", C: hexRange, Err: ErrDisallowedChars}, - {Input: "abcd 1234", C: hexRange, Err: ErrDisallowedChars}, - {Input: "ghijklmno", C: hexRange}, - } + for input, want := range values { + t.Run(input, func(t *testing.T) { + got := testExceptChars(input) - for n, tc := range testCases { - t.Logf("(%d) Testing %q against %q", n, tc.Input, tc.C) - - f := ExceptChars(tc.C) - err := f(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) } } } diff --git a/email_test.go b/email_test.go index 02b5358..f58e68a 100644 --- a/email_test.go +++ b/email_test.go @@ -2,27 +2,45 @@ package validate import ( "errors" + "fmt" "testing" ) -func TestEmail(t *testing.T) { - type TestCase struct { - Input string - Err error - } +func ExampleEmail() { + fmt.Println(Email("not an email")) + // Output: invalid email address +} - testCases := []TestCase{ - {Input: "test@example.com"}, - {Input: "testexample.com", Err: ErrInvalidEmail}, - } +func FuzzEmail(f *testing.F) { + want := ErrInvalidEmail - for n, tc := range testCases { - t.Logf("(%d) Testing %q", n, tc.Input) + f.Fuzz(func(t *testing.T, input string) { + got := Email(input) - err := Email(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) } + }) +} + +func TestEmail(t *testing.T) { + testCases := map[string]error{ + "test@example.com": nil, + "firstname.lastname@some-website.co.uk": nil, + + "not an email": ErrInvalidEmail, + "testexample.com": ErrInvalidEmail, + } + + for input, want := range testCases { + t.Run(input, func(t *testing.T) { + got := Email(input) + + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) } } diff --git a/equal_test.go b/equal_test.go index 194cbea..be344c7 100644 --- a/equal_test.go +++ b/equal_test.go @@ -2,55 +2,29 @@ package validate import ( "errors" + "fmt" "testing" ) -func TestEqualInt(t *testing.T) { - type TestCase struct { - I int - C int - Err error +func TestEqual(t *testing.T) { + testCases := map[string]map[string]error{ + "abc": {"abc": nil, "def": ErrNotEqual.With("abc"), "xyz": ErrNotEqual.With("abc")}, + "def": {"abc": ErrNotEqual.With("def"), "def": nil, "xyz": ErrNotEqual.With("def")}, + "xyz": {"abc": ErrNotEqual.With("xyz"), "def": ErrNotEqual.With("xyz"), "xyz": nil}, } - testCases := []TestCase{ - {I: 1, C: 1}, - {I: 5 ^ 3, C: 5 ^ 3}, - {I: 10, C: 15, Err: ErrNotEqual}, - } + for setup, values := range testCases { + testEqual := Equal(setup) - for i, tc := range testCases { - t.Logf("(%d) Testing %d against %d", i, tc.I, tc.C) + for input, want := range values { + t.Run(fmt.Sprintf("%s/%s", setup, input), func(t *testing.T) { + got := testEqual(input) - f := Equal(tc.C) - err := f(tc.I) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) - } - } -} - -func TestEqualStr(t *testing.T) { - type TestCase struct { - I string - C string - Err error - } - - testCases := []TestCase{ - {I: "abc", C: "abc"}, - {I: "def ghi 123", C: "def ghi 123"}, - {I: "jkl", C: "mno", Err: ErrNotEqual}, - } - - for i, tc := range testCases { - t.Logf("(%d) Testing %s against %s", i, tc.I, tc.C) - - f := Equal(tc.C) - err := f(tc.I) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) } } } diff --git a/error_test.go b/error_test.go index 8a1428c..c7b0796 100644 --- a/error_test.go +++ b/error_test.go @@ -2,48 +2,48 @@ package validate import ( "errors" + "fmt" "testing" ) func TestErrorIs(t *testing.T) { type TestCase struct { - Err error - Target error - Is bool + A error + B error + Want bool } testCases := []TestCase{ - // Is any validation error - {Err: Err, Target: Err, Is: true}, - {Err: ErrDisallowedChars, Target: Err, Is: true}, - {Err: ErrMustBeGreater, Target: Err, Is: true}, + // Want any validation error + {A: Err, B: Err, Want: true}, + {A: ErrDisallowedChars, B: Err, Want: true}, + {A: ErrMustBeGreater, B: Err, Want: true}, - // Is specific validation error - {Err: ErrDisallowedChars, Target: ErrDisallowedChars, Is: true}, - {Err: ErrMustBeGreater, Target: ErrMustBeGreater, Is: true}, + // Want specific validation error + {A: ErrDisallowedChars, B: ErrDisallowedChars, Want: true}, + {A: ErrMustBeGreater, B: ErrMustBeGreater, Want: true}, - // Is not specific validation error - {Err: Err, Target: ErrDisallowedChars}, - {Err: Err, Target: ErrMustBeGreater}, - {Err: ErrMustBeGreater, Target: ErrDisallowedChars}, - {Err: ErrDisallowedChars, Target: ErrMustBeGreater}, + // Want not specific validation error + {A: Err, B: ErrDisallowedChars}, + {A: Err, B: ErrMustBeGreater}, + {A: ErrMustBeGreater, B: ErrDisallowedChars}, + {A: ErrDisallowedChars, B: ErrMustBeGreater}, - // Is not any other error - {Err: ErrDisallowedChars, Target: errors.New("contains disallowed characters")}, - {Err: ErrMustBeGreater, Target: errors.New("must be greater than %v")}, + // Want not any other error + {A: ErrDisallowedChars, B: errors.New("contains disallowed characters")}, + {A: ErrMustBeGreater, B: errors.New("must be greater than %v")}, } - for i, tc := range testCases { - t.Logf("(%d) Testing %v against %v", i, tc.Err, tc.Target) + for _, testCase := range testCases { + a, b, want := testCase.A, testCase.B, testCase.Want - if errors.Is(tc.Err, tc.Target) { - if !tc.Is { - t.Errorf("%v should not equal %v", tc.Err, tc.Target) + t.Run(fmt.Sprintf("%v/%v", a, b), func(t *testing.T) { + got := errors.Is(a, b) + + if got != want { + t.Error("got", got) + t.Error("want", want) } - } else { - if tc.Is { - t.Errorf("%v should equal %v", tc.Err, tc.Target) - } - } + }) } } diff --git a/in_test.go b/in_test.go index 14065b6..c20dced 100644 --- a/in_test.go +++ b/in_test.go @@ -2,59 +2,37 @@ package validate import ( "errors" + "fmt" "testing" ) -func TestInInt(t *testing.T) { - type TestCase struct { - Input int - A []int - Err error - } - - allow := []int{1, 23, 456} - testCases := []TestCase{ - {Input: 1, A: allow}, - {Input: 23, A: allow}, - {Input: 456, A: allow}, - {Input: 789, A: allow, Err: ErrValueNotAllowed}, - } - - for n, tc := range testCases { - t.Logf("(%d) Testing %d against %v", n, tc.Input, tc.A) - - f := In(tc.A...) - err := f(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) - } - } +func ExampleIn() { + testIn := In("abc", "def", "xyz") + fmt.Println(testIn("123")) + // Output: not allowed } -func TestInString(t *testing.T) { - type TestCase struct { - Input string - A []string - Err error +func TestIn(t *testing.T) { + testIn := In("abc", "def", "xyz") + + testCases := map[string]error{ + "abc": nil, + "def": nil, + "xyz": nil, + + "abcd": ErrValueNotAllowed, + "123": ErrValueNotAllowed, + "": ErrValueNotAllowed, } - allow := []string{"abcd", "ef", "1234"} - testCases := []TestCase{ - {Input: "abcd", A: allow}, - {Input: "ef", A: allow}, - {Input: "1234", A: allow}, - {Input: "5678", A: allow, Err: ErrValueNotAllowed}, - } + for input, want := range testCases { + t.Run(input, func(t *testing.T) { + got := testIn(input) - for n, tc := range testCases { - t.Logf("(%d) Testing %q against %v", n, tc.Input, tc.A) - - f := In(tc.A...) - err := f(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) - } + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) } } diff --git a/length.go b/length.go index 1399bb3..0151ad6 100644 --- a/length.go +++ b/length.go @@ -2,8 +2,8 @@ package validate // Validation error. var ( - ErrMustBeLonger = NewError("must be at least %d characters") - ErrMustBeShorter = NewError("must be no more than %d characters") + ErrMustBeLonger = NewError("must contain at least %d characters") + ErrMustBeShorter = NewError("must contain no more than %d characters") ) // MaxLength validates the length of a string as being less than or equal to a given maximum. diff --git a/length_test.go b/length_test.go index e83e8fd..defd24e 100644 --- a/length_test.go +++ b/length_test.go @@ -2,57 +2,60 @@ package validate import ( "errors" + "fmt" "testing" ) +func ExampleMaxLength() { + testMaxLength := MaxLength(8) + fmt.Println(testMaxLength("this string is too long")) + // Output: must contain no more than 8 characters +} + +func ExampleMinLength() { + testMinLength := MinLength(8) + fmt.Println(testMinLength("2short")) + // Output: must contain at least 8 characters +} + func TestMaxLength(t *testing.T) { - type TestCase struct { - Input string - L int - Err error + testCases := map[int]map[string]error{ + 8: {"abcd": nil, "abcdefgh": nil, "abcd efg": nil, "abcdefghi": ErrMustBeShorter.With(8)}, } - testCases := []TestCase{ - {Input: "abcd", L: 8}, - {Input: "abcdefgh", L: 8}, - {Input: "abcd efg", L: 8}, - {Input: "abcdefghi", L: 8, Err: ErrMustBeShorter}, - } + for setup, values := range testCases { + testMaxLength := MaxLength(setup) - for n, tc := range testCases { - t.Logf("(%d) Testing %q against maximum length of %d", n, tc.Input, tc.L) + for input, want := range values { + t.Run(fmt.Sprintf("%d/%s", setup, input), func(t *testing.T) { + got := testMaxLength(input) - f := MaxLength(tc.L) - err := f(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) } } } func TestMinLength(t *testing.T) { - type TestCase struct { - Input string - L int - Err error + testCases := map[int]map[string]error{ + 8: {"abcd": ErrMustBeLonger.With(8), "abcdefgh": nil, "abcd efg": nil, "abcdefghi": nil}, } - testCases := []TestCase{ - {Input: "abcd", L: 8, Err: ErrMustBeLonger}, - {Input: "abcdefgh", L: 8}, - {Input: "abcd efg", L: 8}, - {Input: "abcdefghi", L: 8}, - } + for setup, values := range testCases { + testMinLength := MinLength(setup) - for n, tc := range testCases { - t.Logf("(%d) Testing %q against minimum length of %d", n, tc.Input, tc.L) + for input, want := range values { + t.Run(fmt.Sprintf("%d/%s", setup, input), func(t *testing.T) { + got := testMinLength(input) - f := MinLength(tc.L) - err := f(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) } } } diff --git a/number_test.go b/number_test.go index 712ae62..ec03f15 100644 --- a/number_test.go +++ b/number_test.go @@ -2,167 +2,174 @@ package validate import ( "errors" + "fmt" "testing" ) +func ExampleMax() { + testMax := Max(10, true) + fmt.Println(testMax(10)) + // Output: must be less than 10 +} + +func ExampleMin() { + testMin := Min(10, false) + fmt.Println(testMin(5)) + // Output: must be greater than or equal to 10 +} + func TestMax(t *testing.T) { - type TestCase struct { - Input int - N int - Excl bool - Err error + testCases := map[int]map[bool]map[int]error{ + 10: { + true: {0: nil, 1: nil, 2: nil, 10: ErrMustBeLess.With(10), 100: ErrMustBeLess.With(10)}, + false: {0: nil, 1: nil, 2: nil, 10: nil, 100: ErrMustBeLessOrEqual.With(10)}, + }, } - testCases := []TestCase{ - {Input: 10, N: 0, Err: ErrMustBeLessOrEqual}, - {Input: 10, N: 10}, - {Input: 10, N: 15}, - {Input: 10, N: 10, Excl: true, Err: ErrMustBeLess}, - } + for setup, subSetup := range testCases { + for excl, values := range subSetup { + testMax := Max(setup, excl) - for n, tc := range testCases { - t.Logf("(%d) Testing %d against maximum of %d", n, tc.Input, tc.N) + for input, want := range values { + t.Run(fmt.Sprintf("%d/%v/%d", setup, excl, input), func(t *testing.T) { + got := testMax(input) - f := Max(tc.N, tc.Excl) - err := f(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) + } } } } func TestMaxFloat32(t *testing.T) { - type TestCase struct { - Input float32 - N float32 - Excl bool - Err error + testCases := map[float32]map[bool]map[float32]error{ + 10: { + true: {0: nil, 1: nil, 2: nil, 10: ErrMustBeLess.With(10), 100: ErrMustBeLess.With(10)}, + false: {0: nil, 1: nil, 2: nil, 10: nil, 100: ErrMustBeLessOrEqual.With(10)}, + }, } - testCases := []TestCase{ - {Input: 10, N: 0, Err: ErrMustBeLessOrEqual}, - {Input: 10, N: 10}, - {Input: 10, N: 15}, - {Input: 10, N: 10, Excl: true, Err: ErrMustBeLess}, - } + for setup, subSetup := range testCases { + for excl, values := range subSetup { + testMax := MaxFloat32(setup, excl) - for n, tc := range testCases { - t.Logf("(%d) Testing %g against maximum of %g", n, tc.Input, tc.N) + for input, want := range values { + t.Run(fmt.Sprintf("%f/%v/%f", setup, excl, input), func(t *testing.T) { + got := testMax(input) - f := MaxFloat32(tc.N, tc.Excl) - err := f(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) + } } } } func TestMaxFloat64(t *testing.T) { - type TestCase struct { - Input float64 - N float64 - Excl bool - Err error + testCases := map[float64]map[bool]map[float64]error{ + 10: { + true: {0: nil, 1: nil, 2: nil, 10: ErrMustBeLess.With(10), 100: ErrMustBeLess.With(10)}, + false: {0: nil, 1: nil, 2: nil, 10: nil, 100: ErrMustBeLessOrEqual.With(10)}, + }, } - testCases := []TestCase{ - {Input: 10, N: 0, Err: ErrMustBeLessOrEqual}, - {Input: 10, N: 10}, - {Input: 10, N: 15}, - {Input: 10, N: 10, Excl: true, Err: ErrMustBeLess}, - } + for setup, subSetup := range testCases { + for excl, values := range subSetup { + testMax := MaxFloat64(setup, excl) - for n, tc := range testCases { - t.Logf("(%d) Testing %g against maximum of %g", n, tc.Input, tc.N) + for input, want := range values { + t.Run(fmt.Sprintf("%f/%v/%f", setup, excl, input), func(t *testing.T) { + got := testMax(input) - f := MaxFloat64(tc.N, tc.Excl) - err := f(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) + } } } } func TestMin(t *testing.T) { - type TestCase struct { - Input int - N int - Excl bool - Err error + testCases := map[int]map[bool]map[int]error{ + 10: { + true: {0: ErrMustBeGreater.With(10), 1: ErrMustBeGreater.With(10), 2: ErrMustBeGreater.With(10), 10: ErrMustBeGreater.With(10), 100: nil}, + false: {0: ErrMustBeGreaterOrEqual.With(10), 1: ErrMustBeGreaterOrEqual.With(10), 2: ErrMustBeGreaterOrEqual.With(10), 10: nil, 100: nil}, + }, } - testCases := []TestCase{ - {Input: 10, N: 0}, - {Input: 10, N: 10}, - {Input: 10, N: 15, Err: ErrMustBeGreaterOrEqual}, - {Input: 10, N: 10, Excl: true, Err: ErrMustBeGreater}, - } + for setup, subSetup := range testCases { + for excl, values := range subSetup { + testMin := Min(setup, excl) - for n, tc := range testCases { - t.Logf("(%d) Testing %d against minimum of %d", n, tc.Input, tc.N) + for input, want := range values { + t.Run(fmt.Sprintf("%d/%v/%d", setup, excl, input), func(t *testing.T) { + got := testMin(input) - f := Min(tc.N, tc.Excl) - err := f(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) + } } } } func TestMinFloat32(t *testing.T) { - type TestCase struct { - Input float32 - N float32 - Excl bool - Err error + testCases := map[float32]map[bool]map[float32]error{ + 10: { + true: {0: ErrMustBeGreater.With(10), 1: ErrMustBeGreater.With(10), 2: ErrMustBeGreater.With(10), 10: ErrMustBeGreater.With(10), 100: nil}, + false: {0: ErrMustBeGreaterOrEqual.With(10), 1: ErrMustBeGreaterOrEqual.With(10), 2: ErrMustBeGreaterOrEqual.With(10), 10: nil, 100: nil}, + }, } - testCases := []TestCase{ - {Input: 10, N: 0}, - {Input: 10, N: 10}, - {Input: 10, N: 15, Err: ErrMustBeGreaterOrEqual}, - {Input: 10, N: 10, Excl: true, Err: ErrMustBeGreater}, - } + for setup, subSetup := range testCases { + for excl, values := range subSetup { + testMin := MinFloat32(setup, excl) - for n, tc := range testCases { - t.Logf("(%d) Testing %g against minimum of %g", n, tc.Input, tc.N) + for input, want := range values { + t.Run(fmt.Sprintf("%f/%v/%f", setup, excl, input), func(t *testing.T) { + got := testMin(input) - f := MinFloat32(tc.N, tc.Excl) - err := f(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) + } } } } func TestMinFloat64(t *testing.T) { - type TestCase struct { - Input float64 - N float64 - Excl bool - Err error + testCases := map[float64]map[bool]map[float64]error{ + 10: { + true: {0: ErrMustBeGreater.With(10), 1: ErrMustBeGreater.With(10), 2: ErrMustBeGreater.With(10), 10: ErrMustBeGreater.With(10), 100: nil}, + false: {0: ErrMustBeGreaterOrEqual.With(10), 1: ErrMustBeGreaterOrEqual.With(10), 2: ErrMustBeGreaterOrEqual.With(10), 10: nil, 100: nil}, + }, } - testCases := []TestCase{ - {Input: 10, N: 0}, - {Input: 10, N: 10}, - {Input: 10, N: 15, Err: ErrMustBeGreaterOrEqual}, - {Input: 10, N: 10, Excl: true, Err: ErrMustBeGreater}, - } + for setup, subSetup := range testCases { + for excl, values := range subSetup { + testMin := MinFloat64(setup, excl) - for n, tc := range testCases { - t.Logf("(%d) Testing %g against minimum of %g", n, tc.Input, tc.N) + for input, want := range values { + t.Run(fmt.Sprintf("%f/%v/%f", setup, excl, input), func(t *testing.T) { + got := testMin(input) - f := MinFloat64(tc.N, tc.Excl) - err := f(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) + } } } } diff --git a/size.go b/size.go index fa93c60..b499b5d 100644 --- a/size.go +++ b/size.go @@ -10,7 +10,7 @@ var ( func MaxSize[T any](l int) func([]T) error { return func(value []T) error { if len(value) > l { - return ErrMustHaveFewerItems + return ErrMustHaveFewerItems.With(l) } return nil } @@ -20,7 +20,7 @@ func MaxSize[T any](l int) func([]T) error { func MinSize[T any](l int) func([]T) error { return func(value []T) error { if len(value) < l { - return ErrMustHaveMoreItems + return ErrMustHaveMoreItems.With(l) } return nil } diff --git a/size_test.go b/size_test.go index 0d0c451..6c5a66e 100644 --- a/size_test.go +++ b/size_test.go @@ -2,55 +2,78 @@ package validate import ( "errors" + "fmt" "testing" ) +func ExampleMaxSize() { + testMaxSize := MaxSize[string](3) + fmt.Println(testMaxSize([]string{"abc", "def", "ghi", "jkl"})) + // Output: must have no more than 3 items +} + +func ExampleMinSize() { + testMaxSize := MinSize[string](3) + fmt.Println(testMaxSize([]string{"abc", "def"})) + // Output: must have at least 3 items +} + func TestMaxSize(t *testing.T) { - type TestCase struct { - Input []int - L int - Err error + testCases := map[int]map[int]error{ + 0: {0: nil, 1: ErrMustHaveFewerItems.With(0), 2: ErrMustHaveFewerItems.With(0), 10: ErrMustHaveFewerItems.With(0)}, + 1: {0: nil, 1: nil, 2: ErrMustHaveFewerItems.With(1), 10: ErrMustHaveFewerItems.With(1)}, + 2: {0: nil, 1: nil, 2: nil, 10: ErrMustHaveFewerItems.With(2)}, + 10: {0: nil, 1: nil, 2: nil, 10: nil}, + 100: {0: nil, 1: nil, 2: nil, 10: nil}, } - testCases := []TestCase{ - {Input: []int{1, 2, 3, 4}, L: 8}, - {Input: []int{1, 2, 3, 4, 5, 6, 7, 8}, L: 8}, - {Input: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, L: 8, Err: ErrMustHaveFewerItems}, - } + for max, values := range testCases { + testMaxSize := MaxSize[int](max) - for n, tc := range testCases { - t.Logf("(%d) Testing %q against maximum length of %d", n, tc.Input, tc.L) + for l, want := range values { + t.Run(fmt.Sprintf("%d/%d", max, l), func(t *testing.T) { + input := []int{} + for i := 0; i < l; i++ { + input = append(input, i) + } - f := MaxSize[int](tc.L) - err := f(tc.Input) + got := testMaxSize(input) - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) } } } func TestMinSize(t *testing.T) { - type TestCase struct { - Input []int - L int - Err error + testCases := map[int]map[int]error{ + 0: {0: nil, 1: nil, 2: nil, 10: nil}, + 1: {0: ErrMustHaveMoreItems.With(1), 1: nil, 2: nil, 10: nil}, + 2: {0: ErrMustHaveMoreItems.With(2), 1: ErrMustHaveMoreItems.With(2), 2: nil, 10: nil}, + 10: {0: ErrMustHaveMoreItems.With(10), 1: ErrMustHaveMoreItems.With(10), 2: ErrMustHaveMoreItems.With(10), 10: nil}, + 100: {0: ErrMustHaveMoreItems.With(100), 1: ErrMustHaveMoreItems.With(100), 2: ErrMustHaveMoreItems.With(100), 10: ErrMustHaveMoreItems.With(100)}, } - testCases := []TestCase{ - {Input: []int{1, 2, 3, 4}, L: 8, Err: ErrMustHaveMoreItems}, - {Input: []int{1, 2, 3, 4, 5, 6, 7, 8}, L: 8}, - {Input: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, L: 8}, - } + for min, values := range testCases { + testMinSize := MinSize[int](min) - for n, tc := range testCases { - t.Logf("(%d) Testing %q against minimum length of %d", n, tc.Input, tc.L) + for l, want := range values { + t.Run(fmt.Sprintf("%d/%d", min, l), func(t *testing.T) { + input := []int{} + for i := 0; i < l; i++ { + input = append(input, i) + } - f := MinSize[int](tc.L) - err := f(tc.Input) + got := testMinSize(input) - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) } } } diff --git a/url_test.go b/url_test.go index d788fc3..8f7eb54 100644 --- a/url_test.go +++ b/url_test.go @@ -2,30 +2,33 @@ package validate import ( "errors" + "fmt" "testing" ) +func ExampleURL() { + fmt.Println(URL("not a url")) + // Output: invalid URL +} + func TestURL(t *testing.T) { - type TestCase struct { - Input string - Err error + testCases := map[string]error{ + "http://example.com": nil, + "http://subdomain.example.com": nil, + "http://www.example.com/some-page.html": nil, + + "not a url": ErrInvalidURL, + "subdomain.com": ErrInvalidURL, } - testCases := []TestCase{ - {Input: "http://example.com"}, - {Input: "http://subdomain.example.com"}, - {Input: "http://www.example.com/some-page.html"}, - {Input: "subdomain.com", Err: ErrInvalidURL}, - {Input: "not a url", Err: ErrInvalidURL}, - } + for input, want := range testCases { + t.Run(input, func(t *testing.T) { + got := URL(input) - for n, tc := range testCases { - t.Logf("(%d) Testing %q", n, tc.Input) - - err := URL(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) - } + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) } } diff --git a/uuid_test.go b/uuid_test.go index 7acfc62..c2735d3 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -2,32 +2,35 @@ package validate import ( "errors" + "fmt" "testing" ) +func ExampleUUID() { + fmt.Println(UUID("not a uuid")) + // Output: invalid UUID +} + func TestUUID(t *testing.T) { - type TestCase struct { - Input string - Err error + testCases := map[string]error{ + "00000000-0000-0000-0000-000000000000": nil, + "01234567-89ab-cdef-0123-456789abcdef": nil, + "abcdef01-2345-6789-abcd-ef0123456789": nil, + + "not a uuid": ErrInvalidUUID, + "00000000-00-0000-0000-00000000000000": ErrInvalidUUID, + "00000000000000000000000000000000": ErrInvalidUUID, + "01234567-89ab-cdef-ghij-klmnopqrstuv": ErrInvalidUUID, } - testCases := []TestCase{ - {Input: "00000000-0000-0000-0000-000000000000"}, - {Input: "01234567-89ab-cdef-0123-456789abcdef"}, - {Input: "abcdef01-2345-6789-abcd-ef0123456789"}, - {Input: "Not a UUID", Err: ErrInvalidUUID}, - {Input: "00000000-00-0000-0000-00000000000000", Err: ErrInvalidUUID}, - {Input: "00000000000000000000000000000000", Err: ErrInvalidUUID}, - {Input: "01234567-89ab-cdef-ghij-klmnopqrstuv", Err: ErrInvalidUUID}, - } + for input, want := range testCases { + t.Run(input, func(t *testing.T) { + got := UUID(input) - for n, tc := range testCases { - t.Logf("(%d) Testing %q", n, tc.Input) - - err := UUID(tc.Input) - - if !errors.Is(err, tc.Err) { - t.Errorf("Expected error %v, got %v", tc.Err, err) - } + if !errors.Is(got, want) { + t.Error("got", got) + t.Error("want", want) + } + }) } }