Repository: stretchr/testify
Pull request #953 (2020-05-22 15:07:54)

Issue640 add NotEqualValues()
## Summary
This PR implements NotEqualValues(), as requested (partially) in #640

## Changes
Changes consist of adding the implementation of NotEqualValues(), similar to how currently EqualValues() and NotEquals() are implemented.

## Motivation
When asserting that values do not equal using `NotEquals`, the assertion will already be true if types differ. This can happen easily when using numeric literals, e.g.

```
assert.NotEquals(t, 10, SomeFuncReturningUInt())
```
will hold even if the func returns 10 since the type assigned by the compiler / returned by the func differ.

## Related issues
Implements #640  in part.



Patch:
diff --git a/assert/assertion_format.go b/assert/assertion_format.go
index 9745268..b4c4604 100644
--- a/assert/assertion_format.go
+++ b/assert/assertion_format.go
@@ -467,6 +467,16 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string,
 	return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
 }
 
+// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
+//
+//    assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
+func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
+}
+
 // NotNilf asserts that the specified object is not nil.
 //
 //    assert.NotNilf(t, err, "error message %s", "formatted")
diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go
index 72c1c4b..9bea8d1 100644
--- a/assert/assertion_forward.go
+++ b/assert/assertion_forward.go
@@ -909,6 +909,26 @@ func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndAr
 	return NotEqual(a.t, expected, actual, msgAndArgs...)
 }
 
+// NotEqualValues asserts that two objects are not equal even when converted to the same type
+//
+//    a.NotEqualValues(obj1, obj2)
+func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	return NotEqualValues(a.t, expected, actual, msgAndArgs...)
+}
+
+// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
+//
+//    a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted")
+func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	return NotEqualValuesf(a.t, expected, actual, msg, args...)
+}
+
 // NotEqualf asserts that the specified values are NOT equal.
 //
 //    a.NotEqualf(obj1, obj2, "error message %s", "formatted")
diff --git a/assert/assertions.go b/assert/assertions.go
index 7af9ad2..914a10d 100644
--- a/assert/assertions.go
+++ b/assert/assertions.go
@@ -699,6 +699,21 @@ func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{
 
 }
 
+// NotEqualValues asserts that two objects are not equal even when converted to the same type
+//
+//    assert.NotEqualValues(t, obj1, obj2)
+func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+
+	if ObjectsAreEqualValues(expected, actual) {
+		return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...)
+	}
+
+	return true
+}
+
 // containsElement try loop over the list check if the list includes the element.
 // return (false, false) if impossible.
 // return (true, false) if element was not found.
diff --git a/require/require.go b/require/require.go
index cb99f25..693648f 100644
--- a/require/require.go
+++ b/require/require.go
@@ -1159,6 +1159,32 @@ func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs .
 	t.FailNow()
 }
 
+// NotEqualValues asserts that two objects are not equal even when converted to the same type
+//
+//    assert.NotEqualValues(t, obj1, obj2)
+func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	if assert.NotEqualValues(t, expected, actual, msgAndArgs...) {
+		return
+	}
+	t.FailNow()
+}
+
+// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
+//
+//    assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
+func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	if assert.NotEqualValuesf(t, expected, actual, msg, args...) {
+		return
+	}
+	t.FailNow()
+}
+
 // NotEqualf asserts that the specified values are NOT equal.
 //
 //    assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
diff --git a/require/require_forward.go b/require/require_forward.go
index 5354f45..84fc1c8 100644
--- a/require/require_forward.go
+++ b/require/require_forward.go
@@ -910,6 +910,26 @@ func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndAr
 	NotEqual(a.t, expected, actual, msgAndArgs...)
 }
 
+// NotEqualValues asserts that two objects are not equal even when converted to the same type
+//
+//    a.NotEqualValues(obj1, obj2)
+func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	NotEqualValues(a.t, expected, actual, msgAndArgs...)
+}
+
+// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
+//
+//    a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted")
+func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	NotEqualValuesf(a.t, expected, actual, msg, args...)
+}
+
 // NotEqualf asserts that the specified values are NOT equal.
 //
 //    a.NotEqualf(obj1, obj2, "error message %s", "formatted")


Test patch:
diff --git a/assert/assertions_test.go b/assert/assertions_test.go
index 4f0f41c..7046bc0 100644
--- a/assert/assertions_test.go
+++ b/assert/assertions_test.go
@@ -222,6 +222,10 @@ func TestEqual(t *testing.T) {
 	if Equal(mockT, myType("1"), myType("2")) {
 		t.Error("Equal should return false")
 	}
+	// A case that might be confusing, especially with numeric literals
+	if Equal(mockT, 10, uint(10)) {
+		t.Error("Equal should return false")
+	}
 }
 
 func ptr(i int) *int {
@@ -544,6 +548,70 @@ func TestNotEqual(t *testing.T) {
 	if NotEqual(mockT, &struct{}{}, &struct{}{}) {
 		t.Error("NotEqual should return false")
 	}
+
+	// A case that might be confusing, especially with numeric literals
+	if !NotEqual(mockT, 10, uint(10)) {
+		t.Error("NotEqual should return false")
+	}
+}
+
+func TestNotEqualValues(t *testing.T) {
+
+	mockT := new(testing.T)
+
+	// Same tests as NotEqual since they behave the same when types are irrelevant
+	if !NotEqualValues(mockT, "Hello World", "Hello World!") {
+		t.Error("NotEqualValues should return true")
+	}
+	if !NotEqualValues(mockT, 123, 1234) {
+		t.Error("NotEqualValues should return true")
+	}
+	if !NotEqualValues(mockT, 123.5, 123.55) {
+		t.Error("NotEqualValues should return true")
+	}
+	if !NotEqualValues(mockT, []byte("Hello World"), []byte("Hello World!")) {
+		t.Error("NotEqualValues should return true")
+	}
+	if !NotEqualValues(mockT, nil, new(AssertionTesterConformingObject)) {
+		t.Error("NotEqualValues should return true")
+	}
+	if NotEqualValues(mockT, nil, nil) {
+		t.Error("NotEqualValues should return false")
+	}
+	if NotEqualValues(mockT, "Hello World", "Hello World") {
+		t.Error("NotEqualValues should return false")
+	}
+	if NotEqualValues(mockT, 123, 123) {
+		t.Error("NotEqualValues should return false")
+	}
+	if NotEqualValues(mockT, 123.5, 123.5) {
+		t.Error("NotEqualValues should return false")
+	}
+	if NotEqualValues(mockT, []byte("Hello World"), []byte("Hello World")) {
+		t.Error("NotEqualValues should return false")
+	}
+	if NotEqualValues(mockT, new(AssertionTesterConformingObject), new(AssertionTesterConformingObject)) {
+		t.Error("NotEqualValues should return false")
+	}
+	if NotEqualValues(mockT, &struct{}{}, &struct{}{}) {
+		t.Error("NotEqualValues should return false")
+	}
+
+	// Special cases where NotEqualValues behaves differently
+	funcA := func() int { return 23 }
+	funcB := func() int { return 42 }
+	if !NotEqualValues(mockT, funcA, funcB) {
+		t.Error("NotEqualValues should return true")
+	}
+	if !NotEqualValues(mockT, int(10), int(11)) {
+		t.Error("NotEqualValues should return true")
+	}
+	if NotEqualValues(mockT, int(10), uint(10)) {
+		t.Error("NotEqualValues should return false")
+	}
+	if NotEqualValues(mockT, struct{}{}, struct{}{}) {
+		t.Error("NotEqualValues should return false")
+	}
 }
 
 type A struct {
@@ -2139,6 +2207,7 @@ func TestComparisonAssertionFunc(t *testing.T) {
 		{"isType", (*testing.T)(nil), t, IsType},
 		{"equal", t, t, Equal},
 		{"equalValues", t, t, EqualValues},
+		{"notEqualValues", t, nil, NotEqualValues},
 		{"exactly", t, t, Exactly},
 		{"notEqual", t, nil, NotEqual},
 		{"notContains", []int{1, 2, 3}, 4, NotContains},
diff --git a/assert/forward_assertions_test.go b/assert/forward_assertions_test.go
index 60e5e30..a8240d7 100644
--- a/assert/forward_assertions_test.go
+++ b/assert/forward_assertions_test.go
@@ -154,6 +154,30 @@ func TestNotEqualWrapper(t *testing.T) {
 	}
 }
 
+func TestNotEqualValuesWrapper(t *testing.T) {
+
+	assert := New(new(testing.T))
+
+	if !assert.NotEqualValues("Hello World", "Hello World!") {
+		t.Error("NotEqualValues should return true")
+	}
+	if !assert.NotEqualValues(123, 1234) {
+		t.Error("NotEqualValues should return true")
+	}
+	if !assert.NotEqualValues(123.5, 123.55) {
+		t.Error("NotEqualValues should return true")
+	}
+	if !assert.NotEqualValues([]byte("Hello World"), []byte("Hello World!")) {
+		t.Error("NotEqualValues should return true")
+	}
+	if !assert.NotEqualValues(nil, new(AssertionTesterConformingObject)) {
+		t.Error("NotEqualValues should return true")
+	}
+	if assert.NotEqualValues(10, uint(10)) {
+		t.Error("NotEqualValues should return false")
+	}
+}
+
 func TestContainsWrapper(t *testing.T) {
 
 	assert := New(new(testing.T))
diff --git a/require/requirements_test.go b/require/requirements_test.go
index c4d0afa..c43e989 100644
--- a/require/requirements_test.go
+++ b/require/requirements_test.go
@@ -521,6 +521,7 @@ func TestComparisonAssertionFunc(t *testing.T) {
 		{"equalValues", t, t, EqualValues},
 		{"exactly", t, t, Exactly},
 		{"notEqual", t, nil, NotEqual},
+		{"NotEqualValues", t, nil, NotEqualValues},
 		{"notContains", []int{1, 2, 3}, 4, NotContains},
 		{"subset", []int{1, 2, 3, 4}, []int{2, 3}, Subset},
 		{"notSubset", []int{1, 2, 3, 4}, []int{0, 3}, NotSubset},