Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
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 16 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 32 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 16 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 97 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

Copy link
Copy Markdown

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
Copy Markdown
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{}))
Comment thread
ccoVeille marked this conversation as resolved.
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)))
Comment thread
ccoVeille marked this conversation as resolved.
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
Comment thread
ccoVeille marked this conversation as resolved.
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)))
Comment thread
ccoVeille marked this conversation as resolved.
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 32 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 32 require/require_forward.go

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

Morty Proxy This is a proxified and sanitized view of the page, visit original site.