Repository: stretchr/testify
Pull request #949 (2020-05-22 11:35:00)

Allow comparing custom numeric types




## Summary
Allow comparing custom numeric types without panics. 

## Changes
Prior to this commit, this would not work:

```go
type myInt int
assert.Less(t, myInt(1), myInt(2)) // panic!
```

We add some type conversions, similarly to how `ObjectsAreEqualValues`
work. This allows us to compare `myInt` to `myInt`, as well as `myInt`
to `int`.


## Motivation
It's a pain doing `assert.Less(t, int(myCustomIntValue), 8)` every time I want to compare numbers. 


torkelrogstad: Changed this up a bit: we only do the `Convert` call if we're given types that need conversion. We also make it possible to convert string types. 

Benchmarks with most recent changes: 

```
BenchmarkCompare
BenchmarkCompare-8   	 2755450	       426 ns/op
PASS
Process finished with exit code 0
```

Still a hit compared to master, but I think it's worth it. I don't believe this code path is the most critical one?

Patch:
diff --git a/.travis.yml b/.travis.yml
index f01c8df..e6bb475 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,18 +4,14 @@ os: linux
 jobs:
   include:
     - go: "1.10.x"
-    - go: "1.11.x"
-      env: GO111MODULE=off
-    - go: "1.11.x"
-      env: GO111MODULE=on
-    - go: "1.12.x"
-      env: GO111MODULE=off
-    - go: "1.12.x"
-      env: GO111MODULE=on
     - go: "1.13.x"
       env: GO111MODULE=off
     - go: "1.13.x"
       env: GO111MODULE=on
+    - go: "1.14.x"
+      env: GO111MODULE=off
+    - go: "1.14.x"
+      env: GO111MODULE=on
     - go: master
       env: GO111MODULE=on
     - go: master
diff --git a/assert/assertion_compare.go b/assert/assertion_compare.go
index dc20039..1584af8 100644
--- a/assert/assertion_compare.go
+++ b/assert/assertion_compare.go
@@ -13,12 +13,42 @@ const (
 	compareGreater
 )
 
+var (
+	intType   = reflect.TypeOf(int(1))
+	int8Type  = reflect.TypeOf(int8(1))
+	int16Type = reflect.TypeOf(int16(1))
+	int32Type = reflect.TypeOf(int32(1))
+	int64Type = reflect.TypeOf(int64(1))
+
+	uintType   = reflect.TypeOf(uint(1))
+	uint8Type  = reflect.TypeOf(uint8(1))
+	uint16Type = reflect.TypeOf(uint16(1))
+	uint32Type = reflect.TypeOf(uint32(1))
+	uint64Type = reflect.TypeOf(uint64(1))
+
+	float32Type = reflect.TypeOf(float32(1))
+	float64Type = reflect.TypeOf(float64(1))
+
+	stringType = reflect.TypeOf("")
+)
+
 func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
+	obj1Value := reflect.ValueOf(obj1)
+	obj2Value := reflect.ValueOf(obj2)
+
+	// throughout this switch we try and avoid calling .Convert() if possible,
+	// as this has a pretty big performance impact
 	switch kind {
 	case reflect.Int:
 		{
-			intobj1 := obj1.(int)
-			intobj2 := obj2.(int)
+			intobj1, ok := obj1.(int)
+			if !ok {
+				intobj1 = obj1Value.Convert(intType).Interface().(int)
+			}
+			intobj2, ok := obj2.(int)
+			if !ok {
+				intobj2 = obj2Value.Convert(intType).Interface().(int)
+			}
 			if intobj1 > intobj2 {
 				return compareGreater, true
 			}
@@ -31,8 +61,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 		}
 	case reflect.Int8:
 		{
-			int8obj1 := obj1.(int8)
-			int8obj2 := obj2.(int8)
+			int8obj1, ok := obj1.(int8)
+			if !ok {
+				int8obj1 = obj1Value.Convert(int8Type).Interface().(int8)
+			}
+			int8obj2, ok := obj2.(int8)
+			if !ok {
+				int8obj2 = obj2Value.Convert(int8Type).Interface().(int8)
+			}
 			if int8obj1 > int8obj2 {
 				return compareGreater, true
 			}
@@ -45,8 +81,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 		}
 	case reflect.Int16:
 		{
-			int16obj1 := obj1.(int16)
-			int16obj2 := obj2.(int16)
+			int16obj1, ok := obj1.(int16)
+			if !ok {
+				int16obj1 = obj1Value.Convert(int16Type).Interface().(int16)
+			}
+			int16obj2, ok := obj2.(int16)
+			if !ok {
+				int16obj2 = obj2Value.Convert(int16Type).Interface().(int16)
+			}
 			if int16obj1 > int16obj2 {
 				return compareGreater, true
 			}
@@ -59,8 +101,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 		}
 	case reflect.Int32:
 		{
-			int32obj1 := obj1.(int32)
-			int32obj2 := obj2.(int32)
+			int32obj1, ok := obj1.(int32)
+			if !ok {
+				int32obj1 = obj1Value.Convert(int32Type).Interface().(int32)
+			}
+			int32obj2, ok := obj2.(int32)
+			if !ok {
+				int32obj2 = obj2Value.Convert(int32Type).Interface().(int32)
+			}
 			if int32obj1 > int32obj2 {
 				return compareGreater, true
 			}
@@ -73,8 +121,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 		}
 	case reflect.Int64:
 		{
-			int64obj1 := obj1.(int64)
-			int64obj2 := obj2.(int64)
+			int64obj1, ok := obj1.(int64)
+			if !ok {
+				int64obj1 = obj1Value.Convert(int64Type).Interface().(int64)
+			}
+			int64obj2, ok := obj2.(int64)
+			if !ok {
+				int64obj2 = obj2Value.Convert(int64Type).Interface().(int64)
+			}
 			if int64obj1 > int64obj2 {
 				return compareGreater, true
 			}
@@ -87,8 +141,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 		}
 	case reflect.Uint:
 		{
-			uintobj1 := obj1.(uint)
-			uintobj2 := obj2.(uint)
+			uintobj1, ok := obj1.(uint)
+			if !ok {
+				uintobj1 = obj1Value.Convert(uintType).Interface().(uint)
+			}
+			uintobj2, ok := obj2.(uint)
+			if !ok {
+				uintobj2 = obj2Value.Convert(uintType).Interface().(uint)
+			}
 			if uintobj1 > uintobj2 {
 				return compareGreater, true
 			}
@@ -101,8 +161,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 		}
 	case reflect.Uint8:
 		{
-			uint8obj1 := obj1.(uint8)
-			uint8obj2 := obj2.(uint8)
+			uint8obj1, ok := obj1.(uint8)
+			if !ok {
+				uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8)
+			}
+			uint8obj2, ok := obj2.(uint8)
+			if !ok {
+				uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8)
+			}
 			if uint8obj1 > uint8obj2 {
 				return compareGreater, true
 			}
@@ -115,8 +181,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 		}
 	case reflect.Uint16:
 		{
-			uint16obj1 := obj1.(uint16)
-			uint16obj2 := obj2.(uint16)
+			uint16obj1, ok := obj1.(uint16)
+			if !ok {
+				uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16)
+			}
+			uint16obj2, ok := obj2.(uint16)
+			if !ok {
+				uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16)
+			}
 			if uint16obj1 > uint16obj2 {
 				return compareGreater, true
 			}
@@ -129,8 +201,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 		}
 	case reflect.Uint32:
 		{
-			uint32obj1 := obj1.(uint32)
-			uint32obj2 := obj2.(uint32)
+			uint32obj1, ok := obj1.(uint32)
+			if !ok {
+				uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32)
+			}
+			uint32obj2, ok := obj2.(uint32)
+			if !ok {
+				uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32)
+			}
 			if uint32obj1 > uint32obj2 {
 				return compareGreater, true
 			}
@@ -143,8 +221,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 		}
 	case reflect.Uint64:
 		{
-			uint64obj1 := obj1.(uint64)
-			uint64obj2 := obj2.(uint64)
+			uint64obj1, ok := obj1.(uint64)
+			if !ok {
+				uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64)
+			}
+			uint64obj2, ok := obj2.(uint64)
+			if !ok {
+				uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64)
+			}
 			if uint64obj1 > uint64obj2 {
 				return compareGreater, true
 			}
@@ -157,8 +241,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 		}
 	case reflect.Float32:
 		{
-			float32obj1 := obj1.(float32)
-			float32obj2 := obj2.(float32)
+			float32obj1, ok := obj1.(float32)
+			if !ok {
+				float32obj1 = obj1Value.Convert(float32Type).Interface().(float32)
+			}
+			float32obj2, ok := obj2.(float32)
+			if !ok {
+				float32obj2 = obj2Value.Convert(float32Type).Interface().(float32)
+			}
 			if float32obj1 > float32obj2 {
 				return compareGreater, true
 			}
@@ -171,8 +261,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 		}
 	case reflect.Float64:
 		{
-			float64obj1 := obj1.(float64)
-			float64obj2 := obj2.(float64)
+			float64obj1, ok := obj1.(float64)
+			if !ok {
+				float64obj1 = obj1Value.Convert(float64Type).Interface().(float64)
+			}
+			float64obj2, ok := obj2.(float64)
+			if !ok {
+				float64obj2 = obj2Value.Convert(float64Type).Interface().(float64)
+			}
 			if float64obj1 > float64obj2 {
 				return compareGreater, true
 			}
@@ -185,8 +281,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 		}
 	case reflect.String:
 		{
-			stringobj1 := obj1.(string)
-			stringobj2 := obj2.(string)
+			stringobj1, ok := obj1.(string)
+			if !ok {
+				stringobj1 = obj1Value.Convert(stringType).Interface().(string)
+			}
+			stringobj2, ok := obj2.(string)
+			if !ok {
+				stringobj2 = obj2Value.Convert(stringType).Interface().(string)
+			}
 			if stringobj1 > stringobj2 {
 				return compareGreater, true
 			}
diff --git a/assert/assertion_format.go b/assert/assertion_format.go
index b4c4604..49370eb 100644
--- a/assert/assertion_format.go
+++ b/assert/assertion_format.go
@@ -6,7 +6,6 @@
 package assert
 
 import (
-	io "io"
 	http "net/http"
 	url "net/url"
 	time "time"
@@ -202,11 +201,11 @@ func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, arg
 //  assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msg string, args ...interface{}) bool {
+func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
 	if h, ok := t.(tHelper); ok {
 		h.Helper()
 	}
-	return HTTPBodyContains(t, handler, method, url, values, body, str, append([]interface{}{msg}, args...)...)
+	return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
 }
 
 // HTTPBodyNotContainsf asserts that a specified handler returns a
@@ -215,11 +214,11 @@ func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url
 //  assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msg string, args ...interface{}) bool {
+func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
 	if h, ok := t.(tHelper); ok {
 		h.Helper()
 	}
-	return HTTPBodyNotContains(t, handler, method, url, values, body, str, append([]interface{}{msg}, args...)...)
+	return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
 }
 
 // HTTPErrorf asserts that a specified handler returns an error status code.
diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go
index 9bea8d1..9db8894 100644
--- a/assert/assertion_forward.go
+++ b/assert/assertion_forward.go
@@ -6,7 +6,6 @@
 package assert
 
 import (
-	io "io"
 	http "net/http"
 	url "net/url"
 	time "time"
@@ -386,11 +385,11 @@ func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args .
 //  a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msgAndArgs ...interface{}) bool {
+func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
 	if h, ok := a.t.(tHelper); ok {
 		h.Helper()
 	}
-	return HTTPBodyContains(a.t, handler, method, url, values, body, str, msgAndArgs...)
+	return HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...)
 }
 
 // HTTPBodyContainsf asserts that a specified handler returns a
@@ -399,11 +398,11 @@ func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, u
 //  a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msg string, args ...interface{}) bool {
+func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
 	if h, ok := a.t.(tHelper); ok {
 		h.Helper()
 	}
-	return HTTPBodyContainsf(a.t, handler, method, url, values, body, str, msg, args...)
+	return HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...)
 }
 
 // HTTPBodyNotContains asserts that a specified handler returns a
@@ -412,11 +411,11 @@ func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string,
 //  a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msgAndArgs ...interface{}) bool {
+func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
 	if h, ok := a.t.(tHelper); ok {
 		h.Helper()
 	}
-	return HTTPBodyNotContains(a.t, handler, method, url, values, body, str, msgAndArgs...)
+	return HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...)
 }
 
 // HTTPBodyNotContainsf asserts that a specified handler returns a
@@ -425,11 +424,11 @@ func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string
 //  a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msg string, args ...interface{}) bool {
+func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
 	if h, ok := a.t.(tHelper); ok {
 		h.Helper()
 	}
-	return HTTPBodyNotContainsf(a.t, handler, method, url, values, body, str, msg, args...)
+	return HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...)
 }
 
 // HTTPError asserts that a specified handler returns an error status code.
diff --git a/assert/http_assertions.go b/assert/http_assertions.go
index 30ef7cc..4ed341d 100644
--- a/assert/http_assertions.go
+++ b/assert/http_assertions.go
@@ -2,7 +2,6 @@ package assert
 
 import (
 	"fmt"
-	"io"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -112,13 +111,9 @@ func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, va
 
 // HTTPBody is a helper that returns HTTP body of the response. It returns
 // empty string if building a new request fails.
-func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values, body io.Reader) string {
+func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
 	w := httptest.NewRecorder()
-
-	if values != nil {
-		url = url + "?" + values.Encode()
-	}
-	req, err := http.NewRequest(method, url, body)
+	req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
 	if err != nil {
 		return ""
 	}
@@ -132,13 +127,13 @@ func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values, b
 //  assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, body io.Reader, str interface{}, msgAndArgs ...interface{}) bool {
+func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
 	if h, ok := t.(tHelper); ok {
 		h.Helper()
 	}
-	httpBody := HTTPBody(handler, method, url, values, body)
+	body := HTTPBody(handler, method, url, values)
 
-	contains := strings.Contains(httpBody, fmt.Sprint(str))
+	contains := strings.Contains(body, fmt.Sprint(str))
 	if !contains {
 		Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
 	}
@@ -152,13 +147,13 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string,
 //  assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, body io.Reader, str interface{}, msgAndArgs ...interface{}) bool {
+func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
 	if h, ok := t.(tHelper); ok {
 		h.Helper()
 	}
-	httpBody := HTTPBody(handler, method, url, values, body)
+	body := HTTPBody(handler, method, url, values)
 
-	contains := strings.Contains(httpBody, fmt.Sprint(str))
+	contains := strings.Contains(body, fmt.Sprint(str))
 	if contains {
 		Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
 	}
diff --git a/require/require.go b/require/require.go
index 693648f..ec4624b 100644
--- a/require/require.go
+++ b/require/require.go
@@ -7,7 +7,6 @@ package require
 
 import (
 	assert "github.com/stretchr/testify/assert"
-	io "io"
 	http "net/http"
 	url "net/url"
 	time "time"
@@ -489,11 +488,11 @@ func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...in
 //  assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msgAndArgs ...interface{}) {
+func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) {
 	if h, ok := t.(tHelper); ok {
 		h.Helper()
 	}
-	if assert.HTTPBodyContains(t, handler, method, url, values, body, str, msgAndArgs...) {
+	if assert.HTTPBodyContains(t, handler, method, url, values, str, msgAndArgs...) {
 		return
 	}
 	t.FailNow()
@@ -505,11 +504,11 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url s
 //  assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msg string, args ...interface{}) {
+func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) {
 	if h, ok := t.(tHelper); ok {
 		h.Helper()
 	}
-	if assert.HTTPBodyContainsf(t, handler, method, url, values, body, str, msg, args...) {
+	if assert.HTTPBodyContainsf(t, handler, method, url, values, str, msg, args...) {
 		return
 	}
 	t.FailNow()
@@ -521,11 +520,11 @@ func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url
 //  assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msgAndArgs ...interface{}) {
+func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) {
 	if h, ok := t.(tHelper); ok {
 		h.Helper()
 	}
-	if assert.HTTPBodyNotContains(t, handler, method, url, values, body, str, msgAndArgs...) {
+	if assert.HTTPBodyNotContains(t, handler, method, url, values, str, msgAndArgs...) {
 		return
 	}
 	t.FailNow()
@@ -537,11 +536,11 @@ func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, ur
 //  assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msg string, args ...interface{}) {
+func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) {
 	if h, ok := t.(tHelper); ok {
 		h.Helper()
 	}
-	if assert.HTTPBodyNotContainsf(t, handler, method, url, values, body, str, msg, args...) {
+	if assert.HTTPBodyNotContainsf(t, handler, method, url, values, str, msg, args...) {
 		return
 	}
 	t.FailNow()
diff --git a/require/require_forward.go b/require/require_forward.go
index 84fc1c8..103d7dc 100644
--- a/require/require_forward.go
+++ b/require/require_forward.go
@@ -7,7 +7,6 @@ package require
 
 import (
 	assert "github.com/stretchr/testify/assert"
-	io "io"
 	http "net/http"
 	url "net/url"
 	time "time"
@@ -387,11 +386,11 @@ func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args .
 //  a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msgAndArgs ...interface{}) {
+func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) {
 	if h, ok := a.t.(tHelper); ok {
 		h.Helper()
 	}
-	HTTPBodyContains(a.t, handler, method, url, values, body, str, msgAndArgs...)
+	HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...)
 }
 
 // HTTPBodyContainsf asserts that a specified handler returns a
@@ -400,11 +399,11 @@ func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, u
 //  a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msg string, args ...interface{}) {
+func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) {
 	if h, ok := a.t.(tHelper); ok {
 		h.Helper()
 	}
-	HTTPBodyContainsf(a.t, handler, method, url, values, body, str, msg, args...)
+	HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...)
 }
 
 // HTTPBodyNotContains asserts that a specified handler returns a
@@ -413,11 +412,11 @@ func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string,
 //  a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msgAndArgs ...interface{}) {
+func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) {
 	if h, ok := a.t.(tHelper); ok {
 		h.Helper()
 	}
-	HTTPBodyNotContains(a.t, handler, method, url, values, body, str, msgAndArgs...)
+	HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...)
 }
 
 // HTTPBodyNotContainsf asserts that a specified handler returns a
@@ -426,11 +425,11 @@ func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string
 //  a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
 //
 // Returns whether the assertion was successful (true) or not (false).
-func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, body io.Reader, str interface{}, msg string, args ...interface{}) {
+func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) {
 	if h, ok := a.t.(tHelper); ok {
 		h.Helper()
 	}
-	HTTPBodyNotContainsf(a.t, handler, method, url, values, body, str, msg, args...)
+	HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...)
 }
 
 // HTTPError asserts that a specified handler returns an error status code.


Test patch:
diff --git a/assert/assertion_compare_test.go b/assert/assertion_compare_test.go
index f53c5c9..4f7f441 100644
--- a/assert/assertion_compare_test.go
+++ b/assert/assertion_compare_test.go
@@ -8,23 +8,49 @@ import (
 )
 
 func TestCompare(t *testing.T) {
+	type customInt int
+	type customInt8 int8
+	type customInt16 int16
+	type customInt32 int32
+	type customInt64 int64
+	type customUInt uint
+	type customUInt8 uint8
+	type customUInt16 uint16
+	type customUInt32 uint32
+	type customUInt64 uint64
+	type customFloat32 float32
+	type customFloat64 float64
+	type customString string
 	for _, currCase := range []struct {
 		less    interface{}
 		greater interface{}
 		cType   string
 	}{
+		{less: customString("a"), greater: customString("b"), cType: "string"},
 		{less: "a", greater: "b", cType: "string"},
+		{less: customInt(1), greater: customInt(2), cType: "int"},
 		{less: int(1), greater: int(2), cType: "int"},
+		{less: customInt8(1), greater: customInt8(2), cType: "int8"},
 		{less: int8(1), greater: int8(2), cType: "int8"},
+		{less: customInt16(1), greater: customInt16(2), cType: "int16"},
 		{less: int16(1), greater: int16(2), cType: "int16"},
+		{less: customInt32(1), greater: customInt32(2), cType: "int32"},
 		{less: int32(1), greater: int32(2), cType: "int32"},
+		{less: customInt64(1), greater: customInt64(2), cType: "int64"},
 		{less: int64(1), greater: int64(2), cType: "int64"},
+		{less: customUInt(1), greater: customUInt(2), cType: "uint"},
 		{less: uint8(1), greater: uint8(2), cType: "uint8"},
+		{less: customUInt8(1), greater: customUInt8(2), cType: "uint8"},
 		{less: uint16(1), greater: uint16(2), cType: "uint16"},
+		{less: customUInt16(1), greater: customUInt16(2), cType: "uint16"},
 		{less: uint32(1), greater: uint32(2), cType: "uint32"},
+		{less: customUInt32(1), greater: customUInt32(2), cType: "uint32"},
 		{less: uint64(1), greater: uint64(2), cType: "uint64"},
+		{less: customUInt64(1), greater: customUInt64(2), cType: "uint64"},
 		{less: float32(1.23), greater: float32(2.34), cType: "float32"},
+		{less: customFloat32(1.23), greater: customFloat32(2.23), cType: "float32"},
 		{less: float64(1.23), greater: float64(2.34), cType: "float64"},
+		{less: customFloat64(1.23), greater: customFloat64(2.34), cType: "float64"},
 	} {
 		resLess, isComparable := compare(currCase.less, currCase.greater, reflect.ValueOf(currCase.less).Kind())
 		if !isComparable {
diff --git a/assert/assertions_test.go b/assert/assertions_test.go
index 7046bc0..4d2e8c9 100644
--- a/assert/assertions_test.go
+++ b/assert/assertions_test.go
@@ -101,46 +101,41 @@ type AssertionTesterNonConformingObject struct {
 }
 
 func TestObjectsAreEqual(t *testing.T) {
-
-	if !ObjectsAreEqual("Hello World", "Hello World") {
-		t.Error("objectsAreEqual should return true")
-	}
-	if !ObjectsAreEqual(123, 123) {
-		t.Error("objectsAreEqual should return true")
-	}
-	if !ObjectsAreEqual(123.5, 123.5) {
-		t.Error("objectsAreEqual should return true")
-	}
-	if !ObjectsAreEqual([]byte("Hello World"), []byte("Hello World")) {
-		t.Error("objectsAreEqual should return true")
-	}
-	if !ObjectsAreEqual(nil, nil) {
-		t.Error("objectsAreEqual should return true")
-	}
-	if ObjectsAreEqual(map[int]int{5: 10}, map[int]int{10: 20}) {
-		t.Error("objectsAreEqual should return false")
-	}
-	if ObjectsAreEqual('x', "x") {
-		t.Error("objectsAreEqual should return false")
-	}
-	if ObjectsAreEqual("x", 'x') {
-		t.Error("objectsAreEqual should return false")
-	}
-	if ObjectsAreEqual(0, 0.1) {
-		t.Error("objectsAreEqual should return false")
-	}
-	if ObjectsAreEqual(0.1, 0) {
-		t.Error("objectsAreEqual should return false")
-	}
-	if ObjectsAreEqual(time.Now, time.Now) {
-		t.Error("objectsAreEqual should return false")
-	}
-	if ObjectsAreEqual(func() {}, func() {}) {
-		t.Error("objectsAreEqual should return false")
+	cases := []struct {
+		expected interface{}
+		actual   interface{}
+		result   bool
+	}{
+		// cases that are expected to be equal
+		{"Hello World", "Hello World", true},
+		{123, 123, true},
+		{123.5, 123.5, true},
+		{[]byte("Hello World"), []byte("Hello World"), true},
+		{nil, nil, true},
+
+		// cases that are expected not to be equal
+		{map[int]int{5: 10}, map[int]int{10: 20}, false},
+		{'x', "x", false},
+		{"x", 'x', false},
+		{0, 0.1, false},
+		{0.1, 0, false},
+		{time.Now, time.Now, false},
+		{func() {}, func() {}, false},
+		{uint32(10), int32(10), false},
 	}
-	if ObjectsAreEqual(uint32(10), int32(10)) {
-		t.Error("objectsAreEqual should return false")
+
+	for _, c := range cases {
+		t.Run(fmt.Sprintf("ObjectsAreEqual(%#v, %#v)", c.expected, c.actual), func(t *testing.T) {
+			res := ObjectsAreEqual(c.expected, c.actual)
+
+			if res != c.result {
+				t.Errorf("ObjectsAreEqual(%#v, %#v) should return %#v", c.expected, c.actual, c.result)
+			}
+
+		})
 	}
+
+	// Cases where type differ but values are equal
 	if !ObjectsAreEqualValues(uint32(10), int32(10)) {
 		t.Error("ObjectsAreEqualValues should return true")
 	}
@@ -182,49 +177,44 @@ func TestIsType(t *testing.T) {
 
 }
 
-type myType string
-
 func TestEqual(t *testing.T) {
+	type myType string
 
 	mockT := new(testing.T)
-
-	if !Equal(mockT, "Hello World", "Hello World") {
-		t.Error("Equal should return true")
-	}
-	if !Equal(mockT, 123, 123) {
-		t.Error("Equal should return true")
-	}
-	if !Equal(mockT, 123.5, 123.5) {
-		t.Error("Equal should return true")
-	}
-	if !Equal(mockT, []byte("Hello World"), []byte("Hello World")) {
-		t.Error("Equal should return true")
-	}
-	if !Equal(mockT, nil, nil) {
-		t.Error("Equal should return true")
-	}
-	if !Equal(mockT, int32(123), int32(123)) {
-		t.Error("Equal should return true")
-	}
-	if !Equal(mockT, uint64(123), uint64(123)) {
-		t.Error("Equal should return true")
-	}
-	if !Equal(mockT, myType("1"), myType("1")) {
-		t.Error("Equal should return true")
-	}
-	if !Equal(mockT, &struct{}{}, &struct{}{}) {
-		t.Error("Equal should return true (pointer equality is based on equality of underlying value)")
-	}
 	var m map[string]interface{}
-	if Equal(mockT, m["bar"], "something") {
-		t.Error("Equal should return false")
-	}
-	if Equal(mockT, myType("1"), myType("2")) {
-		t.Error("Equal should return false")
+
+	cases := []struct {
+		expected interface{}
+		actual   interface{}
+		result   bool
+		remark   string
+	}{
+		{"Hello World", "Hello World", true, ""},
+		{123, 123, true, ""},
+		{123.5, 123.5, true, ""},
+		{[]byte("Hello World"), []byte("Hello World"), true, ""},
+		{nil, nil, true, ""},
+		{int32(123), int32(123), true, ""},
+		{uint64(123), uint64(123), true, ""},
+		{myType("1"), myType("1"), true, ""},
+		{&struct{}{}, &struct{}{}, true, "pointer equality is based on equality of underlying value"},
+
+		// Not expected to be equal
+		{m["bar"], "something", false, ""},
+		{myType("1"), myType("2"), false, ""},
+
+		// A case that might be confusing, especially with numeric literals
+		{10, uint(10), false, ""},
 	}
-	// A case that might be confusing, especially with numeric literals
-	if Equal(mockT, 10, uint(10)) {
-		t.Error("Equal should return false")
+
+	for _, c := range cases {
+		t.Run(fmt.Sprintf("Equal(%#v, %#v)", c.expected, c.actual), func(t *testing.T) {
+			res := Equal(mockT, c.expected, c.actual)
+
+			if res != c.result {
+				t.Errorf("Equal(%#v, %#v) should return %#v: %s", c.expected, c.actual, c.result, c.remark)
+			}
+		})
 	}
 }
 
@@ -482,146 +472,119 @@ func TestExactly(t *testing.T) {
 	b := float64(1)
 	c := float32(1)
 	d := float32(2)
-
-	if Exactly(mockT, a, b) {
-		t.Error("Exactly should return false")
-	}
-	if Exactly(mockT, a, d) {
-		t.Error("Exactly should return false")
-	}
-	if !Exactly(mockT, a, c) {
-		t.Error("Exactly should return true")
+	cases := []struct {
+		expected interface{}
+		actual   interface{}
+		result   bool
+	}{
+		{a, b, false},
+		{a, d, false},
+		{a, c, true},
+		{nil, a, false},
+		{a, nil, false},
 	}
 
-	if Exactly(mockT, nil, a) {
-		t.Error("Exactly should return false")
-	}
-	if Exactly(mockT, a, nil) {
-		t.Error("Exactly should return false")
-	}
+	for _, c := range cases {
+		t.Run(fmt.Sprintf("Exactly(%#v, %#v)", c.expected, c.actual), func(t *testing.T) {
+			res := Exactly(mockT, c.expected, c.actual)
 
+			if res != c.result {
+				t.Errorf("Exactly(%#v, %#v) should return %#v", c.expected, c.actual, c.result)
+			}
+		})
+	}
 }
 
 func TestNotEqual(t *testing.T) {
 
 	mockT := new(testing.T)
 
-	if !NotEqual(mockT, "Hello World", "Hello World!") {
-		t.Error("NotEqual should return true")
-	}
-	if !NotEqual(mockT, 123, 1234) {
-		t.Error("NotEqual should return true")
-	}
-	if !NotEqual(mockT, 123.5, 123.55) {
-		t.Error("NotEqual should return true")
-	}
-	if !NotEqual(mockT, []byte("Hello World"), []byte("Hello World!")) {
-		t.Error("NotEqual should return true")
-	}
-	if !NotEqual(mockT, nil, new(AssertionTesterConformingObject)) {
-		t.Error("NotEqual should return true")
-	}
-	funcA := func() int { return 23 }
-	funcB := func() int { return 42 }
-	if NotEqual(mockT, funcA, funcB) {
-		t.Error("NotEqual should return false")
-	}
-	if NotEqual(mockT, nil, nil) {
-		t.Error("NotEqual should return false")
+	cases := []struct {
+		expected interface{}
+		actual   interface{}
+		result   bool
+	}{
+		// cases that are expected not to match
+		{"Hello World", "Hello World!", true},
+		{123, 1234, true},
+		{123.5, 123.55, true},
+		{[]byte("Hello World"), []byte("Hello World!"), true},
+		{nil, new(AssertionTesterConformingObject), true},
+
+		// cases that are expected to match
+		{nil, nil, false},
+		{"Hello World", "Hello World", false},
+		{123, 123, false},
+		{123.5, 123.5, false},
+		{[]byte("Hello World"), []byte("Hello World"), false},
+		{new(AssertionTesterConformingObject), new(AssertionTesterConformingObject), false},
+		{&struct{}{}, &struct{}{}, false},
+		{func() int { return 23 }, func() int { return 24 }, false},
+		// A case that might be confusing, especially with numeric literals
+		{int(10), uint(10), true},
 	}
 
-	if NotEqual(mockT, "Hello World", "Hello World") {
-		t.Error("NotEqual should return false")
-	}
-	if NotEqual(mockT, 123, 123) {
-		t.Error("NotEqual should return false")
-	}
-	if NotEqual(mockT, 123.5, 123.5) {
-		t.Error("NotEqual should return false")
-	}
-	if NotEqual(mockT, []byte("Hello World"), []byte("Hello World")) {
-		t.Error("NotEqual should return false")
-	}
-	if NotEqual(mockT, new(AssertionTesterConformingObject), new(AssertionTesterConformingObject)) {
-		t.Error("NotEqual should return false")
-	}
-	if NotEqual(mockT, &struct{}{}, &struct{}{}) {
-		t.Error("NotEqual should return false")
-	}
+	for _, c := range cases {
+		t.Run(fmt.Sprintf("NotEqual(%#v, %#v)", c.expected, c.actual), func(t *testing.T) {
+			res := NotEqual(mockT, c.expected, c.actual)
 
-	// A case that might be confusing, especially with numeric literals
-	if !NotEqual(mockT, 10, uint(10)) {
-		t.Error("NotEqual should return false")
+			if res != c.result {
+				t.Errorf("NotEqual(%#v, %#v) should return %#v", c.expected, c.actual, c.result)
+			}
+		})
 	}
 }
 
 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")
+	cases := []struct {
+		expected interface{}
+		actual   interface{}
+		result   bool
+	}{
+		// cases that are expected not to match
+		{"Hello World", "Hello World!", true},
+		{123, 1234, true},
+		{123.5, 123.55, true},
+		{[]byte("Hello World"), []byte("Hello World!"), true},
+		{nil, new(AssertionTesterConformingObject), true},
+
+		// cases that are expected to match
+		{nil, nil, false},
+		{"Hello World", "Hello World", false},
+		{123, 123, false},
+		{123.5, 123.5, false},
+		{[]byte("Hello World"), []byte("Hello World"), false},
+		{new(AssertionTesterConformingObject), new(AssertionTesterConformingObject), false},
+		{&struct{}{}, &struct{}{}, false},
+
+		// Different behaviour from NotEqual()
+		{func() int { return 23 }, func() int { return 24 }, true},
+		{int(10), int(11), true},
+		{int(10), uint(10), false},
+
+		{struct{}{}, struct{}{}, 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")
-	}
-}
+	for _, c := range cases {
+		t.Run(fmt.Sprintf("NotEqualValues(%#v, %#v)", c.expected, c.actual), func(t *testing.T) {
+			res := NotEqualValues(mockT, c.expected, c.actual)
 
-type A struct {
-	Name, Value string
+			if res != c.result {
+				t.Errorf("NotEqualValues(%#v, %#v) should return %#v", c.expected, c.actual, c.result)
+			}
+		})
+	}
 }
 
-func TestContains(t *testing.T) {
+func TestContainsNotContains(t *testing.T) {
 
-	mockT := new(testing.T)
+	type A struct {
+		Name, Value string
+	}
 	list := []string{"Foo", "Bar"}
+
 	complexList := []*A{
 		{"b", "c"},
 		{"d", "e"},
@@ -630,33 +593,50 @@ func TestContains(t *testing.T) {
 	}
 	simpleMap := map[interface{}]interface{}{"Foo": "Bar"}
 
-	if !Contains(mockT, "Hello World", "Hello") {
-		t.Error("Contains should return true: \"Hello World\" contains \"Hello\"")
-	}
-	if Contains(mockT, "Hello World", "Salut") {
-		t.Error("Contains should return false: \"Hello World\" does not contain \"Salut\"")
+	cases := []struct {
+		expected interface{}
+		actual   interface{}
+		result   bool
+	}{
+		{"Hello World", "Hello", true},
+		{"Hello World", "Salut", false},
+		{list, "Bar", true},
+		{list, "Salut", false},
+		{complexList, &A{"g", "h"}, true},
+		{complexList, &A{"g", "e"}, false},
+		{simpleMap, "Foo", true},
+		{simpleMap, "Bar", false},
 	}
 
-	if !Contains(mockT, list, "Bar") {
-		t.Error("Contains should return true: \"[\"Foo\", \"Bar\"]\" contains \"Bar\"")
-	}
-	if Contains(mockT, list, "Salut") {
-		t.Error("Contains should return false: \"[\"Foo\", \"Bar\"]\" does not contain \"Salut\"")
-	}
-	if !Contains(mockT, complexList, &A{"g", "h"}) {
-		t.Error("Contains should return true: complexList contains {\"g\", \"h\"}")
-	}
-	if Contains(mockT, complexList, &A{"g", "e"}) {
-		t.Error("Contains should return false: complexList contains {\"g\", \"e\"}")
-	}
-	if Contains(mockT, complexList, &A{"g", "e"}) {
-		t.Error("Contains should return false: complexList contains {\"g\", \"e\"}")
-	}
-	if !Contains(mockT, simpleMap, "Foo") {
-		t.Error("Contains should return true: \"{\"Foo\": \"Bar\"}\" contains \"Foo\"")
+	for _, c := range cases {
+		t.Run(fmt.Sprintf("Contains(%#v, %#v)", c.expected, c.actual), func(t *testing.T) {
+			mockT := new(testing.T)
+			res := Contains(mockT, c.expected, c.actual)
+
+			if res != c.result {
+				if res {
+					t.Errorf("Contains(%#v, %#v) should return true:\n\t%#v contains %#v", c.expected, c.actual, c.expected, c.actual)
+				} else {
+					t.Errorf("Contains(%#v, %#v) should return false:\n\t%#v does not contain %#v", c.expected, c.actual, c.expected, c.actual)
+				}
+			}
+		})
 	}
-	if Contains(mockT, simpleMap, "Bar") {
-		t.Error("Contains should return false: \"{\"Foo\": \"Bar\"}\" does not contains \"Bar\"")
+
+	for _, c := range cases {
+		t.Run(fmt.Sprintf("NotContains(%#v, %#v)", c.expected, c.actual), func(t *testing.T) {
+			mockT := new(testing.T)
+			res := NotContains(mockT, c.expected, c.actual)
+
+			// NotContains should be inverse of Contains. If it's not, something is wrong
+			if res == Contains(mockT, c.expected, c.actual) {
+				if res {
+					t.Errorf("NotContains(%#v, %#v) should return true:\n\t%#v does not contains %#v", c.expected, c.actual, c.expected, c.actual)
+				} else {
+					t.Errorf("NotContains(%#v, %#v) should return false:\n\t%#v contains %#v", c.expected, c.actual, c.expected, c.actual)
+				}
+			}
+		})
 	}
 }
 
@@ -672,90 +652,57 @@ func TestContainsFailMessage(t *testing.T) {
 	}
 }
 
-func TestNotContains(t *testing.T) {
-
-	mockT := new(testing.T)
-	list := []string{"Foo", "Bar"}
-	simpleMap := map[interface{}]interface{}{"Foo": "Bar"}
-
-	if !NotContains(mockT, "Hello World", "Hello!") {
-		t.Error("NotContains should return true: \"Hello World\" does not contain \"Hello!\"")
-	}
-	if NotContains(mockT, "Hello World", "Hello") {
-		t.Error("NotContains should return false: \"Hello World\" contains \"Hello\"")
-	}
-
-	if !NotContains(mockT, list, "Foo!") {
-		t.Error("NotContains should return true: \"[\"Foo\", \"Bar\"]\" does not contain \"Foo!\"")
-	}
-	if NotContains(mockT, list, "Foo") {
-		t.Error("NotContains should return false: \"[\"Foo\", \"Bar\"]\" contains \"Foo\"")
-	}
-	if NotContains(mockT, simpleMap, "Foo") {
-		t.Error("Contains should return true: \"{\"Foo\": \"Bar\"}\" contains \"Foo\"")
-	}
-	if !NotContains(mockT, simpleMap, "Bar") {
-		t.Error("Contains should return false: \"{\"Foo\": \"Bar\"}\" does not contains \"Bar\"")
-	}
-}
-
-func TestSubset(t *testing.T) {
-	mockT := new(testing.T)
+func TestSubsetNotSubset(t *testing.T) {
 
-	if !Subset(mockT, []int{1, 2, 3}, nil) {
-		t.Error("Subset should return true: given subset is nil")
-	}
-	if !Subset(mockT, []int{1, 2, 3}, []int{}) {
-		t.Error("Subset should return true: any set contains the nil set")
-	}
-	if !Subset(mockT, []int{1, 2, 3}, []int{1, 2}) {
-		t.Error("Subset should return true: [1, 2, 3] contains [1, 2]")
-	}
-	if !Subset(mockT, []int{1, 2, 3}, []int{1, 2, 3}) {
-		t.Error("Subset should return true: [1, 2, 3] contains [1, 2, 3]")
-	}
-	if !Subset(mockT, []string{"hello", "world"}, []string{"hello"}) {
-		t.Error("Subset should return true: [\"hello\", \"world\"] contains [\"hello\"]")
-	}
+	// MTestCase adds a custom message to the case
+	cases := []struct {
+		expected interface{}
+		actual   interface{}
+		result   bool
+		message  string
+	}{
+		// cases that are expected to contain
+		{[]int{1, 2, 3}, nil, true, "given subset is nil"},
+		{[]int{1, 2, 3}, []int{}, true, "any set contains the nil set"},
+		{[]int{1, 2, 3}, []int{1, 2}, true, "[1, 2, 3] contains [1, 2]"},
+		{[]int{1, 2, 3}, []int{1, 2, 3}, true, "[1, 2, 3] contains [1, 2, 3"},
+		{[]string{"hello", "world"}, []string{"hello"}, true, "[\"hello\", \"world\"] contains [\"hello\"]"},
 
-	if Subset(mockT, []string{"hello", "world"}, []string{"hello", "testify"}) {
-		t.Error("Subset should return false: [\"hello\", \"world\"] does not contain [\"hello\", \"testify\"]")
-	}
-	if Subset(mockT, []int{1, 2, 3}, []int{4, 5}) {
-		t.Error("Subset should return false: [1, 2, 3] does not contain [4, 5]")
-	}
-	if Subset(mockT, []int{1, 2, 3}, []int{1, 5}) {
-		t.Error("Subset should return false: [1, 2, 3] does not contain [1, 5]")
+		// cases that are expected not to contain
+		{[]string{"hello", "world"}, []string{"hello", "testify"}, false, "[\"hello\", \"world\"] does not contain [\"hello\", \"testify\"]"},
+		{[]int{1, 2, 3}, []int{4, 5}, false, "[1, 2, 3] does not contain [4, 5"},
+		{[]int{1, 2, 3}, []int{1, 5}, false, "[1, 2, 3] does not contain [1, 5]"},
 	}
-}
 
-func TestNotSubset(t *testing.T) {
-	mockT := new(testing.T)
+	for _, c := range cases {
+		t.Run("SubSet: "+c.message, func(t *testing.T) {
 
-	if NotSubset(mockT, []int{1, 2, 3}, nil) {
-		t.Error("NotSubset should return false: given subset is nil")
-	}
-	if NotSubset(mockT, []int{1, 2, 3}, []int{}) {
-		t.Error("NotSubset should return false: any set contains the nil set")
-	}
-	if NotSubset(mockT, []int{1, 2, 3}, []int{1, 2}) {
-		t.Error("NotSubset should return false: [1, 2, 3] contains [1, 2]")
-	}
-	if NotSubset(mockT, []int{1, 2, 3}, []int{1, 2, 3}) {
-		t.Error("NotSubset should return false: [1, 2, 3] contains [1, 2, 3]")
-	}
-	if NotSubset(mockT, []string{"hello", "world"}, []string{"hello"}) {
-		t.Error("NotSubset should return false: [\"hello\", \"world\"] contains [\"hello\"]")
-	}
+			mockT := new(testing.T)
+			res := Subset(mockT, c.expected, c.actual)
 
-	if !NotSubset(mockT, []string{"hello", "world"}, []string{"hello", "testify"}) {
-		t.Error("NotSubset should return true: [\"hello\", \"world\"] does not contain [\"hello\", \"testify\"]")
-	}
-	if !NotSubset(mockT, []int{1, 2, 3}, []int{4, 5}) {
-		t.Error("NotSubset should return true: [1, 2, 3] does not contain [4, 5]")
+			if res != c.result {
+				if res {
+					t.Errorf("Subset should return true: %s", c.message)
+				} else {
+					t.Errorf("Subset should return false: %s", c.message)
+				}
+			}
+		})
 	}
-	if !NotSubset(mockT, []int{1, 2, 3}, []int{1, 5}) {
-		t.Error("NotSubset should return true: [1, 2, 3] does not contain [1, 5]")
+	for _, c := range cases {
+		t.Run("NotSubSet: "+c.message, func(t *testing.T) {
+			mockT := new(testing.T)
+			res := NotSubset(mockT, c.expected, c.actual)
+
+			// NotSubset should match the inverse of Subset. If it doesn't, something is wrong
+			if res == Subset(mockT, c.expected, c.actual) {
+				if res {
+					t.Errorf("NotSubset should return true: %s", c.message)
+				} else {
+					t.Errorf("NotSubset should return false: %s", c.message)
+				}
+			}
+		})
 	}
 }
 
@@ -821,51 +768,41 @@ func Test_includeElement(t *testing.T) {
 func TestElementsMatch(t *testing.T) {
 	mockT := new(testing.T)
 
-	if !ElementsMatch(mockT, nil, nil) {
-		t.Error("ElementsMatch should return true")
-	}
-	if !ElementsMatch(mockT, []int{}, []int{}) {
-		t.Error("ElementsMatch should return true")
-	}
-	if !ElementsMatch(mockT, []int{1}, []int{1}) {
-		t.Error("ElementsMatch should return true")
-	}
-	if !ElementsMatch(mockT, []int{1, 1}, []int{1, 1}) {
-		t.Error("ElementsMatch should return true")
-	}
-	if !ElementsMatch(mockT, []int{1, 2}, []int{1, 2}) {
-		t.Error("ElementsMatch should return true")
-	}
-	if !ElementsMatch(mockT, []int{1, 2}, []int{2, 1}) {
-		t.Error("ElementsMatch should return true")
-	}
-	if !ElementsMatch(mockT, [2]int{1, 2}, [2]int{2, 1}) {
-		t.Error("ElementsMatch should return true")
-	}
-	if !ElementsMatch(mockT, []string{"hello", "world"}, []string{"world", "hello"}) {
-		t.Error("ElementsMatch should return true")
-	}
-	if !ElementsMatch(mockT, []string{"hello", "hello"}, []string{"hello", "hello"}) {
-		t.Error("ElementsMatch should return true")
-	}
-	if !ElementsMatch(mockT, []string{"hello", "hello", "world"}, []string{"hello", "world", "hello"}) {
-		t.Error("ElementsMatch should return true")
-	}
-	if !ElementsMatch(mockT, [3]string{"hello", "hello", "world"}, [3]string{"hello", "world", "hello"}) {
-		t.Error("ElementsMatch should return true")
-	}
-	if !ElementsMatch(mockT, []int{}, nil) {
-		t.Error("ElementsMatch should return true")
+	cases := []struct {
+		expected interface{}
+		actual   interface{}
+		result   bool
+	}{
+		// matching
+		{nil, nil, true},
+
+		{nil, nil, true},
+		{[]int{}, []int{}, true},
+		{[]int{1}, []int{1}, true},
+		{[]int{1, 1}, []int{1, 1}, true},
+		{[]int{1, 2}, []int{1, 2}, true},
+		{[]int{1, 2}, []int{2, 1}, true},
+		{[2]int{1, 2}, [2]int{2, 1}, true},
+		{[]string{"hello", "world"}, []string{"world", "hello"}, true},
+		{[]string{"hello", "hello"}, []string{"hello", "hello"}, true},
+		{[]string{"hello", "hello", "world"}, []string{"hello", "world", "hello"}, true},
+		{[3]string{"hello", "hello", "world"}, [3]string{"hello", "world", "hello"}, true},
+		{[]int{}, nil, true},
+
+		// not matching
+		{[]int{1}, []int{1, 1}, false},
+		{[]int{1, 2}, []int{2, 2}, false},
+		{[]string{"hello", "hello"}, []string{"hello"}, false},
 	}
 
-	if ElementsMatch(mockT, []int{1}, []int{1, 1}) {
-		t.Error("ElementsMatch should return false")
-	}
-	if ElementsMatch(mockT, []int{1, 2}, []int{2, 2}) {
-		t.Error("ElementsMatch should return false")
-	}
-	if ElementsMatch(mockT, []string{"hello", "hello"}, []string{"hello"}) {
-		t.Error("ElementsMatch should return false")
+	for _, c := range cases {
+		t.Run(fmt.Sprintf("ElementsMatch(%#v, %#v)", c.expected, c.actual), func(t *testing.T) {
+			res := ElementsMatch(mockT, c.actual, c.expected)
+
+			if res != c.result {
+				t.Errorf("ElementsMatch(%#v, %#v) should return %v", c.actual, c.expected, c.result)
+			}
+		})
 	}
 }
 
diff --git a/assert/http_assertions_test.go b/assert/http_assertions_test.go
index a898968..991866a 100644
--- a/assert/http_assertions_test.go
+++ b/assert/http_assertions_test.go
@@ -2,10 +2,8 @@ package assert
 
 import (
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"net/url"
-	"strings"
 	"testing"
 )
 
@@ -122,6 +120,11 @@ func TestHTTPStatusesWrapper(t *testing.T) {
 	assert.Equal(mockAssert.HTTPError(httpError, "GET", "/", nil), true)
 }
 
+func httpHelloName(w http.ResponseWriter, r *http.Request) {
+	name := r.FormValue("name")
+	w.Write([]byte(fmt.Sprintf("Hello, %s!", name)))
+}
+
 func TestHTTPRequestWithNoParams(t *testing.T) {
 	var got *http.Request
 	handler := func(w http.ResponseWriter, r *http.Request) {
@@ -155,49 +158,25 @@ func TestHttpBody(t *testing.T) {
 	assert := New(t)
 	mockT := new(testing.T)
 
-	assert.True(HTTPBodyContains(mockT, httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "Hello, World!"))
-	assert.True(HTTPBodyContains(mockT, httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "World"))
-	assert.False(HTTPBodyContains(mockT, httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "world"))
+	assert.True(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
+	assert.True(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
+	assert.False(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
 
-	assert.False(HTTPBodyNotContains(mockT, httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "Hello, World!"))
-	assert.False(HTTPBodyNotContains(mockT, httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "World"))
-	assert.True(HTTPBodyNotContains(mockT, httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "world"))
-
-	assert.True(HTTPBodyContains(mockT, httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "Hello, World!"))
-
-	body := strings.NewReader("I will get this request body back as response!!")
-	assert.True(HTTPBodyContains(mockT, httpPostHandler, "POST", "/", nil, body, "I will get this request body back as response!!"))
-	assert.True(HTTPBodyNotContains(mockT, httpPostHandler, "POST", "/", nil, body, "world"))
+	assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
+	assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
+	assert.True(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
 }
 
 func TestHttpBodyWrappers(t *testing.T) {
 	assert := New(t)
 	mockAssert := New(new(testing.T))
 
-	assert.True(mockAssert.HTTPBodyContains(httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "Hello, World!"))
-	assert.True(mockAssert.HTTPBodyContains(httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "World"))
-	assert.False(mockAssert.HTTPBodyContains(httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "world"))
-
-	assert.False(mockAssert.HTTPBodyNotContains(httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "Hello, World!"))
-	assert.False(mockAssert.HTTPBodyNotContains(httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "World"))
-	assert.True(mockAssert.HTTPBodyNotContains(httpGetHelloNameHandler, "GET", "/", url.Values{"name": []string{"World"}}, nil, "world"))
+	assert.True(mockAssert.HTTPBodyContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
+	assert.True(mockAssert.HTTPBodyContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
+	assert.False(mockAssert.HTTPBodyContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
 
-	body := strings.NewReader("I will get this request body back as response!!")
-	assert.True(mockAssert.HTTPBodyContains(httpPostHandler, "POST", "/", nil, body, "I will get this request body back as response!!"))
-	assert.True(mockAssert.HTTPBodyNotContains(httpPostHandler, "POST", "/", nil, body, "world"))
-}
-
-func httpGetHelloNameHandler(w http.ResponseWriter, r *http.Request) {
-	name := r.FormValue("name")
-	w.Write([]byte(fmt.Sprintf("Hello, %s!", name)))
-}
-
-func httpPostHandler(w http.ResponseWriter, r *http.Request) {
-	body, err := ioutil.ReadAll(r.Body)
-	if err != nil {
-		http.Error(w, "can't read body", http.StatusBadRequest)
-		return
-	}
+	assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
+	assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
+	assert.True(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
 
-	w.Write(body)
 }