Repository: stretchr/testify (go)

Issue:
NotImplements
A useful pattern is to have a function take a `thing` that doesn't implement an interface and return `anotherThing` that does. These glue functions work really well to connect different parts of an application, and create a useful abstraction across interfaces.

The way I test these functions is with `assert.Implements(t, interface, anotherThing)` on the result of the function, and `_, ok := thing.(interface); assert.False(ok)` on the input for good measure.

It would be _oh so convenient_ if there were an `assert.NotImplements(t, interface, thing)` I could run instead, and it would be fairly trivial to add to the API as it stands. If this isn't the kind of change that's likely to happen, is there a more semantically reasonable way of doing this?

Pull request description:
Add `NotImplements` and variants
## Summary
Add `assert.NotImplements` (and variants)

## Changes
Added `assert.NotImplements` (and variants)

## Motivation
There was no way to test an object did not implement a specific interface yet. Its counterpart does exist, so it was somewhat surprising to not be able to test the inverse.

```golang
import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	
	"github.com/stretchr/testify/assert"
        "github.com/stretchr/testify/require"
)


func Test_NotASigner(t *testing.T) {
	e, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
        require.NoError(t, err)
        assert.NotImplements(t, (*crypto.Signer)(nil), e.Public()) // public key only isn't a signer
}

```

## Related issues
Closes #1065 


Patch:
diff --git a/assert/assertion_format.go b/assert/assertion_format.go
index 53f9c8e..7416d97 100644
--- a/assert/assertion_format.go
+++ b/assert/assertion_format.go
@@ -613,6 +613,16 @@ func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interf
 	return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
 }
 
+// NotImplementsf asserts that an object does not implement the specified interface.
+//
+//	assert.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
+func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	return NotImplements(t, interfaceObject, object, 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 5a2974b..1d8f3c0 100644
--- a/assert/assertion_forward.go
+++ b/assert/assertion_forward.go
@@ -1218,6 +1218,26 @@ func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...in
 	return NotErrorIsf(a.t, err, target, msg, args...)
 }
 
+// NotImplements asserts that an object does not implement the specified interface.
+//
+//	a.NotImplements((*MyInterface)(nil), new(MyObject))
+func (a *Assertions) NotImplements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	return NotImplements(a.t, interfaceObject, object, msgAndArgs...)
+}
+
+// NotImplementsf asserts that an object does not implement the specified interface.
+//
+//	a.NotImplementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
+func (a *Assertions) NotImplementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	return NotImplementsf(a.t, interfaceObject, object, msg, args...)
+}
+
 // NotNil asserts that the specified object is not nil.
 //
 //	a.NotNil(err)
diff --git a/assert/assertions.go b/assert/assertions.go
index 1e55fbf..72dc25b 100644
--- a/assert/assertions.go
+++ b/assert/assertions.go
@@ -392,6 +392,25 @@ func Implements(t TestingT, interfaceObject interface{}, object interface{}, msg
 	return true
 }
 
+// NotImplements asserts that an object does not implement the specified interface.
+//
+//	assert.NotImplements(t, (*MyInterface)(nil), new(MyObject))
+func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	interfaceType := reflect.TypeOf(interfaceObject).Elem()
+
+	if object == nil {
+		return Fail(t, fmt.Sprintf("Cannot check if nil does not implement %v", interfaceType), msgAndArgs...)
+	}
+	if reflect.TypeOf(object).Implements(interfaceType) {
+		return Fail(t, fmt.Sprintf("%T implements %v", object, interfaceType), msgAndArgs...)
+	}
+
+	return true
+}
+
 // IsType asserts that the specified objects are of the same type.
 func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
 	if h, ok := t.(tHelper); ok {
diff --git a/require/require.go b/require/require.go
index fa3792b..b6bb4cf 100644
--- a/require/require.go
+++ b/require/require.go
@@ -1543,6 +1543,32 @@ func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interf
 	t.FailNow()
 }
 
+// NotImplements asserts that an object does not implement the specified interface.
+//
+//	assert.NotImplements(t, (*MyInterface)(nil), new(MyObject))
+func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	if assert.NotImplements(t, interfaceObject, object, msgAndArgs...) {
+		return
+	}
+	t.FailNow()
+}
+
+// NotImplementsf asserts that an object does not implement the specified interface.
+//
+//	assert.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
+func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	if assert.NotImplementsf(t, interfaceObject, object, msg, args...) {
+		return
+	}
+	t.FailNow()
+}
+
 // NotNil asserts that the specified object is not nil.
 //
 //	assert.NotNil(t, err)
diff --git a/require/require_forward.go b/require/require_forward.go
index 99e19ea..7378b64 100644
--- a/require/require_forward.go
+++ b/require/require_forward.go
@@ -1219,6 +1219,26 @@ func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...in
 	NotErrorIsf(a.t, err, target, msg, args...)
 }
 
+// NotImplements asserts that an object does not implement the specified interface.
+//
+//	a.NotImplements((*MyInterface)(nil), new(MyObject))
+func (a *Assertions) NotImplements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	NotImplements(a.t, interfaceObject, object, msgAndArgs...)
+}
+
+// NotImplementsf asserts that an object does not implement the specified interface.
+//
+//	a.NotImplementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
+func (a *Assertions) NotImplementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	NotImplementsf(a.t, interfaceObject, object, msg, args...)
+}
+
 // NotNil asserts that the specified object is not nil.
 //
 //	a.NotNil(err)


Test patch:
diff --git a/assert/assertions_test.go b/assert/assertions_test.go
index 9a44f95..39739cd 100644
--- a/assert/assertions_test.go
+++ b/assert/assertions_test.go
@@ -449,6 +449,22 @@ func TestImplements(t *testing.T) {
 
 }
 
+func TestNotImplements(t *testing.T) {
+
+	mockT := new(testing.T)
+
+	if !NotImplements(mockT, (*AssertionTesterInterface)(nil), new(AssertionTesterNonConformingObject)) {
+		t.Error("NotImplements method should return true: AssertionTesterNonConformingObject does not implement AssertionTesterInterface")
+	}
+	if NotImplements(mockT, (*AssertionTesterInterface)(nil), new(AssertionTesterConformingObject)) {
+		t.Error("NotImplements method should return false: AssertionTesterConformingObject implements AssertionTesterInterface")
+	}
+	if NotImplements(mockT, (*AssertionTesterInterface)(nil), nil) {
+		t.Error("NotImplements method should return false: nil can't be checked to be implementing AssertionTesterInterface or not")
+	}
+
+}
+
 func TestIsType(t *testing.T) {
 
 	mockT := new(testing.T)