Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions assert/assertion_format.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 24 additions & 8 deletions assert/assertion_forward.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions assert/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -773,10 +773,19 @@ func isEmpty(object interface{}) bool {
}
}

// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
// Empty asserts that the given value is "empty".
//
// [Zero values] are "empty".
//
// Arrays are "empty" if every element is the zero value of the type (stricter than "empty").
//
// Slices, maps and channels with zero length are "empty".
//
// Pointer values are "empty" if the pointer is nil or if the pointed value is "empty".
//
// assert.Empty(t, obj)
//
// [Zero values]: https://go.dev/ref/spec#The_zero_value
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
pass := isEmpty(object)
if !pass {
Expand All @@ -789,8 +798,7 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return pass
}

// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
// NotEmpty asserts that the specified object is NOT [Empty].
//
// if assert.NotEmpty(t, obj) {
// assert.Equal(t, "two", obj[1])
Expand Down
97 changes: 96 additions & 1 deletion assert/assertions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1728,24 +1728,119 @@ func Test_isEmpty(t *testing.T) {

True(t, isEmpty(""))
True(t, isEmpty(nil))
True(t, isEmpty(error(nil)))
True(t, isEmpty((*int)(nil)))
True(t, isEmpty((*string)(nil)))
True(t, isEmpty(new(string)))
Comment on lines +1731 to +1734

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your list is pretty extensive.

You could test some interfaces with nil values but have a types associated with them but that might be a bit overkill. For example, you can have err being nil according to the assertions but is not equal to nil

type MyError struct{}

func (e *MyError) Error() string {
	return "my error"
}

func TestMyError_isNil(t *testing.T) {
	var e *MyError
	err := error(e)
	assert.False(t, err == nil)
	assert.Empty(t, err)
	assert.Nil(t, err)
	assert.Zero(t, err)
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because testify works at runtime it does not have visibility of your code's interface types, they get always converted into interface{}, eg:

var e *MyError // type: *MyError, value: nil
assert.Empty(t, e)
...
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
	object // type: interface{}, value: (*MyError)(nil)
...
err := error(e) // type: error, value: (*MyError)(nil)
assert.Empty(t, err)
...
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
	object // type: interface{}, value: (*MyError)(nil)

Put simply, testify cannot tell that you passed an interface.

True(t, isEmpty([]string{}))
True(t, isEmpty([]string(nil)))
True(t, isEmpty([]byte(nil)))
True(t, isEmpty([]byte{}))
True(t, isEmpty([]byte("")))
True(t, isEmpty([]bool(nil)))
True(t, isEmpty([]bool{}))
True(t, isEmpty([]interface{}(nil)))
True(t, isEmpty([]interface{}{}))
True(t, isEmpty(struct{}{}))
True(t, isEmpty(&struct{}{}))
True(t, isEmpty(struct{ A int }{A: 0}))
True(t, isEmpty(struct{ a int }{a: 0}))
True(t, isEmpty(struct {
a int
B int
}{a: 0, B: 0}))
True(t, isEmpty(0))
True(t, isEmpty(int(0)))
True(t, isEmpty(int8(0)))
True(t, isEmpty(int16(0)))
True(t, isEmpty(uint16(0)))
True(t, isEmpty(int32(0)))
True(t, isEmpty(uint32(0)))
True(t, isEmpty(int64(0)))
True(t, isEmpty(uint64(0)))
True(t, isEmpty('\u0000')) // rune => int32
True(t, isEmpty(float32(0)))
True(t, isEmpty(float64(0)))
True(t, isEmpty(0i)) // complex
True(t, isEmpty(0.0i)) // complex
True(t, isEmpty(false))
True(t, isEmpty(new(bool)))
True(t, isEmpty(map[string]string{}))
True(t, isEmpty(map[string]string(nil)))
True(t, isEmpty(new(time.Time)))
True(t, isEmpty(time.Time{}))
True(t, isEmpty(make(chan struct{})))
True(t, isEmpty([1]int{}))
True(t, isEmpty(chan struct{}(nil)))
True(t, isEmpty(chan<- struct{}(nil)))
True(t, isEmpty(make(chan struct{})))
True(t, isEmpty(make(chan<- struct{})))
True(t, isEmpty(make(chan struct{}, 1)))
True(t, isEmpty(make(chan<- struct{}, 1)))
True(t, isEmpty([1]int{0}))
True(t, isEmpty([2]int{0, 0}))
True(t, isEmpty([8]int{}))
True(t, isEmpty([...]int{7: 0}))
True(t, isEmpty([...]bool{false, false}))
True(t, isEmpty(errors.New(""))) // BEWARE
True(t, isEmpty([]error{}))
True(t, isEmpty([]error(nil)))
True(t, isEmpty(&[1]int{0}))
True(t, isEmpty(&[2]int{0, 0}))
False(t, isEmpty("something"))
False(t, isEmpty(errors.New("something")))
False(t, isEmpty([]string{"something"}))
False(t, isEmpty(1))
False(t, isEmpty(int(1)))
False(t, isEmpty(uint(1)))
False(t, isEmpty(byte(1)))
False(t, isEmpty(int8(1)))
False(t, isEmpty(uint8(1)))
False(t, isEmpty(int16(1)))
False(t, isEmpty(uint16(1)))
False(t, isEmpty(int32(1)))
False(t, isEmpty(uint32(1)))
False(t, isEmpty(int64(1)))
False(t, isEmpty(uint64(1)))
False(t, isEmpty('A')) // rune => int32
False(t, isEmpty(true))
False(t, isEmpty(1.0))
False(t, isEmpty(1i)) // complex
False(t, isEmpty([]byte{0})) // elements values are ignored for slices
False(t, isEmpty([]byte{0, 0})) // elements values are ignored for slices
False(t, isEmpty([]string{""})) // elements values are ignored for slices
False(t, isEmpty([]string{"a"})) // elements values are ignored for slices
False(t, isEmpty([]bool{false})) // elements values are ignored for slices
False(t, isEmpty([]bool{true})) // elements values are ignored for slices
False(t, isEmpty([]error{errors.New("xxx")}))
False(t, isEmpty([]error{nil})) // BEWARE
False(t, isEmpty([]error{errors.New("")})) // BEWARE
False(t, isEmpty(map[string]string{"Hello": "World"}))
False(t, isEmpty(map[string]string{"": ""}))
False(t, isEmpty(map[string]string{"foo": ""}))
False(t, isEmpty(map[string]string{"": "foo"}))
False(t, isEmpty(chWithValue))
False(t, isEmpty([1]bool{true}))
False(t, isEmpty([2]bool{false, true}))
False(t, isEmpty([...]bool{10: true}))
False(t, isEmpty([]int{0}))
False(t, isEmpty([]int{42}))
False(t, isEmpty([1]int{42}))
False(t, isEmpty([2]int{0, 42}))
False(t, isEmpty(&[1]int{42}))
False(t, isEmpty(&[2]int{0, 42}))
False(t, isEmpty([1]*int{new(int)})) // array elements must be the zero value, not any Empty value
False(t, isEmpty(struct{ A int }{A: 42}))
False(t, isEmpty(struct{ a int }{a: 42}))
False(t, isEmpty(struct{ a *int }{a: new(int)})) // fields must be the zero value, not any Empty value
False(t, isEmpty(struct{ a []int }{a: []int{}})) // fields must be the zero value, not any Empty value
False(t, isEmpty(struct {
a int
B int
}{a: 0, B: 42}))
False(t, isEmpty(struct {
a int
B int
}{a: 42, B: 0}))
}

func TestEmpty(t *testing.T) {
Expand Down
32 changes: 24 additions & 8 deletions require/require.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 24 additions & 8 deletions require/require_forward.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.