From c531656da0a229a418e7d161dcd2cbb1a96f3e08 Mon Sep 17 00:00:00 2001 From: pfinch Date: Fri, 26 Apr 2024 18:32:14 +1000 Subject: [PATCH 1/4] introducing no field is empty assertion --- assert/assertion_format.go | 8 ++++ assert/assertion_forward.go | 16 +++++++ assert/assertions.go | 35 +++++++++++++++ assert/assertions_test.go | 85 +++++++++++++++++++++++++++++++++++++ require/require.go | 22 ++++++++++ require/require_forward.go | 16 +++++++ 6 files changed, 182 insertions(+) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 714404125..e8d221fa5 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -556,6 +556,14 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { return NoError(t, err, append([]interface{}{msg}, args...)...) } +// NoFieldIsEmptyf asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +func NoFieldIsEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NoFieldIsEmpty(t, object, append([]interface{}{msg}, args...)...) +} + // NoFileExistsf checks whether a file does not exist in a given path. It fails // if the path points to an existing _file_ only. func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) bool { diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index 38e253ebb..d6f54d29b 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -1104,6 +1104,22 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { return NoErrorf(a.t, err, msg, args...) } +// NoFieldIsEmpty asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +func (a *Assertions) NoFieldIsEmpty(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NoFieldIsEmpty(a.t, object, msgAndArgs...) +} + +// NoFieldIsEmptyf asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +func (a *Assertions) NoFieldIsEmptyf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NoFieldIsEmptyf(a.t, object, msg, args...) +} + // NoFileExists checks whether a file does not exist in a given path. It fails // if the path points to an existing _file_ only. func (a *Assertions) NoFileExists(path string, msgAndArgs ...interface{}) bool { diff --git a/assert/assertions.go b/assert/assertions.go index effd026d5..ac25b45ee 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2274,3 +2274,38 @@ func buildErrorChainString(err error, withType bool) string { } return chain } + +// NoFieldIsEmpty asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +func NoFieldIsEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if reflect.TypeOf(object).Kind() == reflect.Ptr { + return NoFieldIsEmpty(t, reflect.ValueOf(object).Elem().Interface(), msgAndArgs) + } + + if h, ok := t.(tHelper); ok { + h.Helper() + } + + objectType := reflect.TypeOf(object) + if objectType.Kind() != reflect.Struct { + return Fail(t, "Input must be a struct or eventually reference one", msgAndArgs...) + } + + var emptyFields []string + objectValue := reflect.ValueOf(object) + for i := 0; i < objectType.NumField(); i++ { + field := objectType.Field(i) + if !field.IsExported() { + continue + } + + if isEmpty(objectValue.Field(i).Interface()) { + emptyFields = append(emptyFields, field.Name) + } + } + + if len(emptyFields) > 0 { + return Fail(t, fmt.Sprintf("Object contained empty fields: %s", strings.Join(emptyFields, ", ")), msgAndArgs...) + } + + return true +} diff --git a/assert/assertions_test.go b/assert/assertions_test.go index c8d59d640..c5f9f5dae 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3664,3 +3664,88 @@ func TestNotErrorAs(t *testing.T) { }) } } + +func TestNoFieldIsEmpty(t *testing.T) { + str := "a string" + tests := []struct { + name string + input interface{} + result bool + resultErrMsg string + }{ + { + name: "success", + input: struct { + Float64 float64 + Func func() + Int int + Interface interface{} + Pointer *string + Slice []string + String string + Struct struct{ String string } + }{ + Float64: 1.5, + Func: func() {}, + Int: 1, + Interface: "interface", + Pointer: &str, + Slice: []string{"a", "b"}, + String: "a string", + Struct: struct{ String string }{String: "a nested string"}, + }, + result: true, + }, + { + name: "success_pointer", + input: &struct { + String string + }{ + String: "a string", + }, + result: true, + }, + { + name: "success_unexported", + input: struct{ unexported string }{}, + result: true, + }, + { + name: "failure", + input: struct { + Float64 float64 + Func func() + Int int + Interface interface{} + Pointer *string + Slice []string + String string + Struct struct{ String string } + }{}, + result: false, + resultErrMsg: "Object contained empty fields: Float64, Func, Int, Interface, Pointer, Slice, String, Struct\n", + }, + { + name: "failure_partial", + input: struct { + StringA string + StringB string + }{StringA: "not_empty"}, + result: false, + resultErrMsg: "Object contained empty fields: StringB\n", + }, + { + name: "failure_wrong_type", + input: "a string is not a struct", + result: false, + resultErrMsg: "Input must be a struct or eventually reference one\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockT := new(captureTestingT) + result := NoFieldIsEmpty(mockT, tt.input) + mockT.checkResultAndErrMsg(t, tt.result, result, tt.resultErrMsg) + }) + } +} diff --git a/require/require.go b/require/require.go index 6cd133417..cb79c8d15 100644 --- a/require/require.go +++ b/require/require.go @@ -1399,6 +1399,28 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { t.FailNow() } +// NoFieldIsEmpty asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +func NoFieldIsEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoFieldIsEmpty(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoFieldIsEmptyf asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +func NoFieldIsEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoFieldIsEmptyf(t, object, msg, args...) { + return + } + t.FailNow() +} + // NoFileExists checks whether a file does not exist in a given path. It fails // if the path points to an existing _file_ only. func NoFileExists(t TestingT, path string, msgAndArgs ...interface{}) { diff --git a/require/require_forward.go b/require/require_forward.go index f6192dfea..24ab59821 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -1105,6 +1105,22 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { NoErrorf(a.t, err, msg, args...) } +// NoFieldIsEmpty asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +func (a *Assertions) NoFieldIsEmpty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoFieldIsEmpty(a.t, object, msgAndArgs...) +} + +// NoFieldIsEmptyf asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +func (a *Assertions) NoFieldIsEmptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoFieldIsEmptyf(a.t, object, msg, args...) +} + // NoFileExists checks whether a file does not exist in a given path. It fails // if the path points to an existing _file_ only. func (a *Assertions) NoFileExists(path string, msgAndArgs ...interface{}) { From 060a6c1c0389e6128e2dfaafb1537e76a4d4f840 Mon Sep 17 00:00:00 2001 From: pfinch Date: Thu, 6 Jun 2024 09:22:31 +1000 Subject: [PATCH 2/4] updating NoFieldIsEmpty documentation --- assert/assertion_format.go | 4 +++- assert/assertion_forward.go | 8 ++++++-- assert/assertions.go | 4 +++- require/require.go | 8 ++++++-- require/require_forward.go | 8 ++++++-- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index e8d221fa5..b5e8fe354 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -556,7 +556,9 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { return NoError(t, err, append([]interface{}{msg}, args...)...) } -// NoFieldIsEmptyf asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +// NoFieldIsEmptyf asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is empty (following +// the definition of empty used in [Empty]). func NoFieldIsEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index d6f54d29b..cea701ffd 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -1104,7 +1104,9 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { return NoErrorf(a.t, err, msg, args...) } -// NoFieldIsEmpty asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +// NoFieldIsEmpty asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is empty (following +// the definition of empty used in [Empty]). func (a *Assertions) NoFieldIsEmpty(object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1112,7 +1114,9 @@ func (a *Assertions) NoFieldIsEmpty(object interface{}, msgAndArgs ...interface{ return NoFieldIsEmpty(a.t, object, msgAndArgs...) } -// NoFieldIsEmptyf asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +// NoFieldIsEmptyf asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is empty (following +// the definition of empty used in [Empty]). func (a *Assertions) NoFieldIsEmptyf(object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() diff --git a/assert/assertions.go b/assert/assertions.go index ac25b45ee..6bdb27d83 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2275,7 +2275,9 @@ func buildErrorChainString(err error, withType bool) string { return chain } -// NoFieldIsEmpty asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +// NoFieldIsEmpty asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is empty (following +// the definition of empty used in [Empty]). func NoFieldIsEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if reflect.TypeOf(object).Kind() == reflect.Ptr { return NoFieldIsEmpty(t, reflect.ValueOf(object).Elem().Interface(), msgAndArgs) diff --git a/require/require.go b/require/require.go index cb79c8d15..15f3dbea4 100644 --- a/require/require.go +++ b/require/require.go @@ -1399,7 +1399,9 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { t.FailNow() } -// NoFieldIsEmpty asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +// NoFieldIsEmpty asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is empty (following +// the definition of empty used in [Empty]). func NoFieldIsEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1410,7 +1412,9 @@ func NoFieldIsEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { t.FailNow() } -// NoFieldIsEmptyf asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +// NoFieldIsEmptyf asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is empty (following +// the definition of empty used in [Empty]). func NoFieldIsEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/require/require_forward.go b/require/require_forward.go index 24ab59821..2397bac30 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -1105,7 +1105,9 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { NoErrorf(a.t, err, msg, args...) } -// NoFieldIsEmpty asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +// NoFieldIsEmpty asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is empty (following +// the definition of empty used in [Empty]). func (a *Assertions) NoFieldIsEmpty(object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1113,7 +1115,9 @@ func (a *Assertions) NoFieldIsEmpty(object interface{}, msgAndArgs ...interface{ NoFieldIsEmpty(a.t, object, msgAndArgs...) } -// NoFieldIsEmptyf asserts that object, which must be a struct or eventually reference to one, has no empty exported fields. +// NoFieldIsEmptyf asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is empty (following +// the definition of empty used in [Empty]). func (a *Assertions) NoFieldIsEmptyf(object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() From 974779ef32900994e72d7bc1c339b4c49ca9e1be Mon Sep 17 00:00:00 2001 From: Peter Finch Date: Thu, 29 May 2025 20:09:26 +1000 Subject: [PATCH 3/4] replacing NoFieldIsEmpty with NoFieldIsZero --- assert/assertion_format.go | 13 ++++++++----- assert/assertion_forward.go | 26 ++++++++++++++++---------- assert/assertions.go | 23 +++++++++++++---------- assert/assertions_test.go | 34 +++++++++++++++++++++------------- require/require.go | 26 ++++++++++++++++---------- require/require_forward.go | 26 ++++++++++++++++---------- 6 files changed, 90 insertions(+), 58 deletions(-) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index b5e8fe354..79c11b807 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -556,14 +556,17 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { return NoError(t, err, append([]interface{}{msg}, args...)...) } -// NoFieldIsEmptyf asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is empty (following -// the definition of empty used in [Empty]). -func NoFieldIsEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { +// NoFieldIsZerof asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is zero. +// +// The assertion is not recursive, meaning it only checks that the exported +// fields of the struct (including any embedded structs) are not zero values. +// It does not check any fields of nested or embedded structs. +func NoFieldIsZerof(t TestingT, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } - return NoFieldIsEmpty(t, object, append([]interface{}{msg}, args...)...) + return NoFieldIsZero(t, object, append([]interface{}{msg}, args...)...) } // NoFileExistsf checks whether a file does not exist in a given path. It fails diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index cea701ffd..dd68e1f71 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -1104,24 +1104,30 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { return NoErrorf(a.t, err, msg, args...) } -// NoFieldIsEmpty asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is empty (following -// the definition of empty used in [Empty]). -func (a *Assertions) NoFieldIsEmpty(object interface{}, msgAndArgs ...interface{}) bool { +// NoFieldIsZero asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is zero. +// +// The assertion is not recursive, meaning it only checks that the exported +// fields of the struct (including any embedded structs) are not zero values. +// It does not check any fields of nested or embedded structs. +func (a *Assertions) NoFieldIsZero(object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() } - return NoFieldIsEmpty(a.t, object, msgAndArgs...) + return NoFieldIsZero(a.t, object, msgAndArgs...) } -// NoFieldIsEmptyf asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is empty (following -// the definition of empty used in [Empty]). -func (a *Assertions) NoFieldIsEmptyf(object interface{}, msg string, args ...interface{}) bool { +// NoFieldIsZerof asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is zero. +// +// The assertion is not recursive, meaning it only checks that the exported +// fields of the struct (including any embedded structs) are not zero values. +// It does not check any fields of nested or embedded structs. +func (a *Assertions) NoFieldIsZerof(object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() } - return NoFieldIsEmptyf(a.t, object, msg, args...) + return NoFieldIsZerof(a.t, object, msg, args...) } // NoFileExists checks whether a file does not exist in a given path. It fails diff --git a/assert/assertions.go b/assert/assertions.go index 6bdb27d83..b0b50632c 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2275,18 +2275,21 @@ func buildErrorChainString(err error, withType bool) string { return chain } -// NoFieldIsEmpty asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is empty (following -// the definition of empty used in [Empty]). -func NoFieldIsEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - if reflect.TypeOf(object).Kind() == reflect.Ptr { - return NoFieldIsEmpty(t, reflect.ValueOf(object).Elem().Interface(), msgAndArgs) - } - +// NoFieldIsZero asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is zero. +// +// The assertion is not recursive, meaning it only checks that the exported +// fields of the struct (including any embedded structs) are not zero values. +// It does not check any fields of nested or embedded structs. +func NoFieldIsZero(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() } + if reflect.TypeOf(object).Kind() == reflect.Ptr { + return NoFieldIsZero(t, reflect.ValueOf(object).Elem().Interface(), msgAndArgs) + } + objectType := reflect.TypeOf(object) if objectType.Kind() != reflect.Struct { return Fail(t, "Input must be a struct or eventually reference one", msgAndArgs...) @@ -2300,13 +2303,13 @@ func NoFieldIsEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) b continue } - if isEmpty(objectValue.Field(i).Interface()) { + if objectValue.Field(i).IsZero() { emptyFields = append(emptyFields, field.Name) } } if len(emptyFields) > 0 { - return Fail(t, fmt.Sprintf("Object contained empty fields: %s", strings.Join(emptyFields, ", ")), msgAndArgs...) + return Fail(t, fmt.Sprintf("Object contained fields with zero values: %s", strings.Join(emptyFields, ", ")), msgAndArgs...) } return true diff --git a/assert/assertions_test.go b/assert/assertions_test.go index c5f9f5dae..d62c96831 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3665,8 +3665,13 @@ func TestNotErrorAs(t *testing.T) { } } -func TestNoFieldIsEmpty(t *testing.T) { +func TestNoFieldIsZero(t *testing.T) { str := "a string" + type Embeddable struct { + StringA string + StringB string + } + tests := []struct { name string input interface{} @@ -3676,6 +3681,7 @@ func TestNoFieldIsEmpty(t *testing.T) { { name: "success", input: struct { + Embeddable Float64 float64 Func func() Int int @@ -3683,16 +3689,17 @@ func TestNoFieldIsEmpty(t *testing.T) { Pointer *string Slice []string String string - Struct struct{ String string } + Struct struct{ StringA, StringB string } }{ - Float64: 1.5, - Func: func() {}, - Int: 1, - Interface: "interface", - Pointer: &str, - Slice: []string{"a", "b"}, - String: "a string", - Struct: struct{ String string }{String: "a nested string"}, + Embeddable: Embeddable{StringA: "string"}, // For Embeddable to be non-zero, only one field its fields needs to be non-zero + Float64: 1.5, + Func: func() {}, + Int: 1, + Interface: "interface", + Pointer: &str, + Slice: []string{"a", "b"}, + String: "a string", + Struct: struct{ StringA, StringB string }{StringA: "a nested string"}, }, result: true, }, @@ -3713,6 +3720,7 @@ func TestNoFieldIsEmpty(t *testing.T) { { name: "failure", input: struct { + Embeddable Float64 float64 Func func() Int int @@ -3723,7 +3731,7 @@ func TestNoFieldIsEmpty(t *testing.T) { Struct struct{ String string } }{}, result: false, - resultErrMsg: "Object contained empty fields: Float64, Func, Int, Interface, Pointer, Slice, String, Struct\n", + resultErrMsg: "Object contained fields with zero values: Embeddable, Float64, Func, Int, Interface, Pointer, Slice, String, Struct\n", }, { name: "failure_partial", @@ -3732,7 +3740,7 @@ func TestNoFieldIsEmpty(t *testing.T) { StringB string }{StringA: "not_empty"}, result: false, - resultErrMsg: "Object contained empty fields: StringB\n", + resultErrMsg: "Object contained fields with zero values: StringB\n", }, { name: "failure_wrong_type", @@ -3744,7 +3752,7 @@ func TestNoFieldIsEmpty(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockT := new(captureTestingT) - result := NoFieldIsEmpty(mockT, tt.input) + result := NoFieldIsZero(mockT, tt.input) mockT.checkResultAndErrMsg(t, tt.result, result, tt.resultErrMsg) }) } diff --git a/require/require.go b/require/require.go index 15f3dbea4..a63573dff 100644 --- a/require/require.go +++ b/require/require.go @@ -1399,27 +1399,33 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { t.FailNow() } -// NoFieldIsEmpty asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is empty (following -// the definition of empty used in [Empty]). -func NoFieldIsEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { +// NoFieldIsZero asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is zero. +// +// The assertion is not recursive, meaning it only checks that the exported +// fields of the struct (including any embedded structs) are not zero values. +// It does not check any fields of nested or embedded structs. +func NoFieldIsZero(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() } - if assert.NoFieldIsEmpty(t, object, msgAndArgs...) { + if assert.NoFieldIsZero(t, object, msgAndArgs...) { return } t.FailNow() } -// NoFieldIsEmptyf asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is empty (following -// the definition of empty used in [Empty]). -func NoFieldIsEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { +// NoFieldIsZerof asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is zero. +// +// The assertion is not recursive, meaning it only checks that the exported +// fields of the struct (including any embedded structs) are not zero values. +// It does not check any fields of nested or embedded structs. +func NoFieldIsZerof(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() } - if assert.NoFieldIsEmptyf(t, object, msg, args...) { + if assert.NoFieldIsZerof(t, object, msg, args...) { return } t.FailNow() diff --git a/require/require_forward.go b/require/require_forward.go index 2397bac30..120ca8862 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -1105,24 +1105,30 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { NoErrorf(a.t, err, msg, args...) } -// NoFieldIsEmpty asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is empty (following -// the definition of empty used in [Empty]). -func (a *Assertions) NoFieldIsEmpty(object interface{}, msgAndArgs ...interface{}) { +// NoFieldIsZero asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is zero. +// +// The assertion is not recursive, meaning it only checks that the exported +// fields of the struct (including any embedded structs) are not zero values. +// It does not check any fields of nested or embedded structs. +func (a *Assertions) NoFieldIsZero(object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() } - NoFieldIsEmpty(a.t, object, msgAndArgs...) + NoFieldIsZero(a.t, object, msgAndArgs...) } -// NoFieldIsEmptyf asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is empty (following -// the definition of empty used in [Empty]). -func (a *Assertions) NoFieldIsEmptyf(object interface{}, msg string, args ...interface{}) { +// NoFieldIsZerof asserts that object, which must be a struct or eventually +// reference to one, has no exported field with a value that is zero. +// +// The assertion is not recursive, meaning it only checks that the exported +// fields of the struct (including any embedded structs) are not zero values. +// It does not check any fields of nested or embedded structs. +func (a *Assertions) NoFieldIsZerof(object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() } - NoFieldIsEmptyf(a.t, object, msg, args...) + NoFieldIsZerof(a.t, object, msg, args...) } // NoFileExists checks whether a file does not exist in a given path. It fails From 27408d847bfdd8a188bd344de75818d230985fba Mon Sep 17 00:00:00 2001 From: Peter Finch Date: Thu, 29 May 2025 21:27:28 +1000 Subject: [PATCH 4/4] NoFieldIsZero also checks unexported fields --- assert/assertion_format.go | 8 ++-- assert/assertion_forward.go | 16 +++---- assert/assertions.go | 12 ++--- assert/assertions_test.go | 89 ++++++++++++++++++++++++++++--------- require/require.go | 16 +++---- require/require_forward.go | 16 +++---- 6 files changed, 100 insertions(+), 57 deletions(-) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 79c11b807..a0dc33383 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -557,11 +557,11 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { } // NoFieldIsZerof asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is zero. +// reference to one, has no field with a value that is zero. // -// The assertion is not recursive, meaning it only checks that the exported -// fields of the struct (including any embedded structs) are not zero values. -// It does not check any fields of nested or embedded structs. +// The assertion is not recursive, meaning it only checks that the fields +// of the struct (embedded structs are considered fields) are not zero values. +// It does not check the fields of nested or embedded structs. func NoFieldIsZerof(t TestingT, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index dd68e1f71..ced10af9b 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -1105,11 +1105,11 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { } // NoFieldIsZero asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is zero. +// reference to one, has no field with a value that is zero. // -// The assertion is not recursive, meaning it only checks that the exported -// fields of the struct (including any embedded structs) are not zero values. -// It does not check any fields of nested or embedded structs. +// The assertion is not recursive, meaning it only checks that the fields +// of the struct (embedded structs are considered fields) are not zero values. +// It does not check the fields of nested or embedded structs. func (a *Assertions) NoFieldIsZero(object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1118,11 +1118,11 @@ func (a *Assertions) NoFieldIsZero(object interface{}, msgAndArgs ...interface{} } // NoFieldIsZerof asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is zero. +// reference to one, has no field with a value that is zero. // -// The assertion is not recursive, meaning it only checks that the exported -// fields of the struct (including any embedded structs) are not zero values. -// It does not check any fields of nested or embedded structs. +// The assertion is not recursive, meaning it only checks that the fields +// of the struct (embedded structs are considered fields) are not zero values. +// It does not check the fields of nested or embedded structs. func (a *Assertions) NoFieldIsZerof(object interface{}, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() diff --git a/assert/assertions.go b/assert/assertions.go index b0b50632c..d9de6c4db 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2276,11 +2276,11 @@ func buildErrorChainString(err error, withType bool) string { } // NoFieldIsZero asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is zero. +// reference to one, has no field with a value that is zero. // -// The assertion is not recursive, meaning it only checks that the exported -// fields of the struct (including any embedded structs) are not zero values. -// It does not check any fields of nested or embedded structs. +// The assertion is not recursive, meaning it only checks that the fields +// of the struct (embedded structs are considered fields) are not zero values. +// It does not check the fields of nested or embedded structs. func NoFieldIsZero(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -2299,10 +2299,6 @@ func NoFieldIsZero(t TestingT, object interface{}, msgAndArgs ...interface{}) bo objectValue := reflect.ValueOf(object) for i := 0; i < objectType.NumField(); i++ { field := objectType.Field(i) - if !field.IsExported() { - continue - } - if objectValue.Field(i).IsZero() { emptyFields = append(emptyFields, field.Name) } diff --git a/assert/assertions_test.go b/assert/assertions_test.go index d62c96831..4dd4f2872 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3679,9 +3679,8 @@ func TestNoFieldIsZero(t *testing.T) { resultErrMsg string }{ { - name: "success", + name: "pass_exported_fields", input: struct { - Embeddable Float64 float64 Func func() Int int @@ -3690,21 +3689,52 @@ func TestNoFieldIsZero(t *testing.T) { Slice []string String string Struct struct{ StringA, StringB string } + }{ + Float64: 1.5, + Func: func() {}, + Int: 1, + Interface: "interface", + Pointer: &str, + Slice: []string{"a", "b"}, + String: "a string", + Struct: struct{ StringA, StringB string }{StringA: "a nested string"}, + }, + result: true, + }, + { + name: "pass_unexported_fields", + input: struct { + aFloat64 float64 + aFunc func() + aInt int + aInterface interface{} + aPointer *string + aSlice []string + aString string + aStruct struct{ stringA, stringB string } + }{ + aFloat64: 1.5, + aFunc: func() {}, + aInt: 1, + aInterface: "interface", + aPointer: &str, + aSlice: []string{"a", "b"}, + aString: "a string", + aStruct: struct{ stringA, stringB string }{stringA: "a nested string"}, + }, + result: true, + }, + { + name: "pass_embedded", + input: struct { + Embeddable }{ Embeddable: Embeddable{StringA: "string"}, // For Embeddable to be non-zero, only one field its fields needs to be non-zero - Float64: 1.5, - Func: func() {}, - Int: 1, - Interface: "interface", - Pointer: &str, - Slice: []string{"a", "b"}, - String: "a string", - Struct: struct{ StringA, StringB string }{StringA: "a nested string"}, }, result: true, }, { - name: "success_pointer", + name: "pass_pointer", input: &struct { String string }{ @@ -3713,14 +3743,8 @@ func TestNoFieldIsZero(t *testing.T) { result: true, }, { - name: "success_unexported", - input: struct{ unexported string }{}, - result: true, - }, - { - name: "failure", + name: "fail_exported_fields", input: struct { - Embeddable Float64 float64 Func func() Int int @@ -3731,10 +3755,33 @@ func TestNoFieldIsZero(t *testing.T) { Struct struct{ String string } }{}, result: false, - resultErrMsg: "Object contained fields with zero values: Embeddable, Float64, Func, Int, Interface, Pointer, Slice, String, Struct\n", + resultErrMsg: "Object contained fields with zero values: Float64, Func, Int, Interface, Pointer, Slice, String, Struct\n", + }, + { + name: "fail_unexported_fields", + input: struct { + aFloat64 float64 + aFunc func() + aInt int + aInterface interface{} + aPointer *string + aSlice []string + aString string + aStruct struct{ stringA, stringB string } + }{}, + result: false, + resultErrMsg: "Object contained fields with zero values: aFloat64, aFunc, aInt, aInterface, aPointer, aSlice, aString, aStruct\n", + }, + { + name: "failure_embedded", + input: struct { + Embeddable + }{}, + result: false, + resultErrMsg: "Object contained fields with zero values: Embeddable\n", }, { - name: "failure_partial", + name: "fail_some_fields_non_zero", input: struct { StringA string StringB string @@ -3743,7 +3790,7 @@ func TestNoFieldIsZero(t *testing.T) { resultErrMsg: "Object contained fields with zero values: StringB\n", }, { - name: "failure_wrong_type", + name: "fail_wrong_type", input: "a string is not a struct", result: false, resultErrMsg: "Input must be a struct or eventually reference one\n", diff --git a/require/require.go b/require/require.go index a63573dff..65824db9a 100644 --- a/require/require.go +++ b/require/require.go @@ -1400,11 +1400,11 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { } // NoFieldIsZero asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is zero. +// reference to one, has no field with a value that is zero. // -// The assertion is not recursive, meaning it only checks that the exported -// fields of the struct (including any embedded structs) are not zero values. -// It does not check any fields of nested or embedded structs. +// The assertion is not recursive, meaning it only checks that the fields +// of the struct (embedded structs are considered fields) are not zero values. +// It does not check the fields of nested or embedded structs. func NoFieldIsZero(t TestingT, object interface{}, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -1416,11 +1416,11 @@ func NoFieldIsZero(t TestingT, object interface{}, msgAndArgs ...interface{}) { } // NoFieldIsZerof asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is zero. +// reference to one, has no field with a value that is zero. // -// The assertion is not recursive, meaning it only checks that the exported -// fields of the struct (including any embedded structs) are not zero values. -// It does not check any fields of nested or embedded structs. +// The assertion is not recursive, meaning it only checks that the fields +// of the struct (embedded structs are considered fields) are not zero values. +// It does not check the fields of nested or embedded structs. func NoFieldIsZerof(t TestingT, object interface{}, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/require/require_forward.go b/require/require_forward.go index 120ca8862..d602c8dd3 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -1106,11 +1106,11 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { } // NoFieldIsZero asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is zero. +// reference to one, has no field with a value that is zero. // -// The assertion is not recursive, meaning it only checks that the exported -// fields of the struct (including any embedded structs) are not zero values. -// It does not check any fields of nested or embedded structs. +// The assertion is not recursive, meaning it only checks that the fields +// of the struct (embedded structs are considered fields) are not zero values. +// It does not check the fields of nested or embedded structs. func (a *Assertions) NoFieldIsZero(object interface{}, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -1119,11 +1119,11 @@ func (a *Assertions) NoFieldIsZero(object interface{}, msgAndArgs ...interface{} } // NoFieldIsZerof asserts that object, which must be a struct or eventually -// reference to one, has no exported field with a value that is zero. +// reference to one, has no field with a value that is zero. // -// The assertion is not recursive, meaning it only checks that the exported -// fields of the struct (including any embedded structs) are not zero values. -// It does not check any fields of nested or embedded structs. +// The assertion is not recursive, meaning it only checks that the fields +// of the struct (embedded structs are considered fields) are not zero values. +// It does not check the fields of nested or embedded structs. func (a *Assertions) NoFieldIsZerof(object interface{}, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper()