diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 9397c8170..f6e68bf27 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -546,6 +546,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 d2065beda..bd04d2db9 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -1084,6 +1084,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 a291b1fe6..516e597af 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -2107,3 +2107,38 @@ func buildErrorChainString(err error) 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 0ae36cd92..4a9e56e0e 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -3228,3 +3228,88 @@ func TestErrorAs(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 5d9bc7caf..4a593d56d 100644 --- a/require/require.go +++ b/require/require.go @@ -1373,6 +1373,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 6fed4cb03..7c33761ef 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -1085,6 +1085,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{}) {