diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 714404125..a0dc33383 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -556,6 +556,19 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { return NoError(t, err, append([]interface{}{msg}, args...)...) } +// NoFieldIsZerof asserts that object, which must be a struct or eventually +// reference to one, has no field with a value that is zero. +// +// 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() + } + return NoFieldIsZero(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..ced10af9b 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -1104,6 +1104,32 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { return NoErrorf(a.t, err, msg, args...) } +// NoFieldIsZero asserts that object, which must be a struct or eventually +// reference to one, has no field with a value that is zero. +// +// 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() + } + return NoFieldIsZero(a.t, object, msgAndArgs...) +} + +// NoFieldIsZerof asserts that object, which must be a struct or eventually +// reference to one, has no field with a value that is zero. +// +// 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() + } + return NoFieldIsZerof(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..d9de6c4db 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2274,3 +2274,39 @@ func buildErrorChainString(err error, withType bool) string { } return chain } + +// NoFieldIsZero asserts that object, which must be a struct or eventually +// reference to one, has no field with a value that is zero. +// +// 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() + } + + 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...) + } + + var emptyFields []string + objectValue := reflect.ValueOf(object) + for i := 0; i < objectType.NumField(); i++ { + field := objectType.Field(i) + if objectValue.Field(i).IsZero() { + emptyFields = append(emptyFields, field.Name) + } + } + + if len(emptyFields) > 0 { + 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 c8d59d640..4dd4f2872 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3664,3 +3664,143 @@ func TestNotErrorAs(t *testing.T) { }) } } + +func TestNoFieldIsZero(t *testing.T) { + str := "a string" + type Embeddable struct { + StringA string + StringB string + } + + tests := []struct { + name string + input interface{} + result bool + resultErrMsg string + }{ + { + name: "pass_exported_fields", + input: struct { + Float64 float64 + Func func() + Int int + Interface interface{} + Pointer *string + 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 + }, + result: true, + }, + { + name: "pass_pointer", + input: &struct { + String string + }{ + String: "a string", + }, + result: true, + }, + { + name: "fail_exported_fields", + 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 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: "fail_some_fields_non_zero", + input: struct { + StringA string + StringB string + }{StringA: "not_empty"}, + result: false, + resultErrMsg: "Object contained fields with zero values: StringB\n", + }, + { + name: "fail_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 := NoFieldIsZero(mockT, tt.input) + mockT.checkResultAndErrMsg(t, tt.result, result, tt.resultErrMsg) + }) + } +} diff --git a/require/require.go b/require/require.go index 6cd133417..65824db9a 100644 --- a/require/require.go +++ b/require/require.go @@ -1399,6 +1399,38 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { t.FailNow() } +// NoFieldIsZero asserts that object, which must be a struct or eventually +// reference to one, has no field with a value that is zero. +// +// 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() + } + if assert.NoFieldIsZero(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoFieldIsZerof asserts that object, which must be a struct or eventually +// reference to one, has no field with a value that is zero. +// +// 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() + } + if assert.NoFieldIsZerof(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..d602c8dd3 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -1105,6 +1105,32 @@ func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { NoErrorf(a.t, err, msg, args...) } +// NoFieldIsZero asserts that object, which must be a struct or eventually +// reference to one, has no field with a value that is zero. +// +// 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() + } + NoFieldIsZero(a.t, object, msgAndArgs...) +} + +// NoFieldIsZerof asserts that object, which must be a struct or eventually +// reference to one, has no field with a value that is zero. +// +// 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() + } + NoFieldIsZerof(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{}) {