Repository: stretchr/testify
Pull request #1018 (2020-10-31 14:30:27)

Add positive/negative assertion
## Summary
<!-- High-level, one sentence summary of what this PR accomplishes -->

Adding positive/negative assertion functions

## Changes

What do you think about these changes?

<!-- * Description of change 1 -->
<!-- * Description of change 2 -->
<!-- ... -->

- Adding function `Positive` to assert that the specified element is positive
- Adding function `Negative` to assert that the specified element is negative

## Motivation
<!-- Why were the changes necessary. -->

When we want to assert that some value is positive or negative, we test like below.

```go
func TestHoge(t *testing.T) {
    assert.Greater(t, 1, 0) // want to assert positive
    assert.Greater(t, 1.23, 0.0) // want to assert positive
    assert.Less(t, -1, 0) // want to assert negative
    assert.Less(t, -1.23, 0) // want to assert negative
}
```

By adding `Positive`/`Negative` assert functions, we can simplify it like an example usage below.

<!-- ## Example usage (if applicable) -->

```go
func TestHoge(t *testing.T) {
    assert.Positive(t, 1)
    assert.Positive(t, 1.23)
    assert.Negative(t, -1)
    assert.Negative(t, -1.23)

    assert := assert.New(t)
    assert.Positive(1)
    assert.Positive(1.23)
    assert.Negative(-1)
    assert.Negative(-1.23)
}
```

## Related issues
<!-- Put `Closes #XXXX` for each issue number this PR fixes/closes -->
nothing


Patch:
diff --git a/assert/assertion_compare.go b/assert/assertion_compare.go
index 1584af8..41649d2 100644
--- a/assert/assertion_compare.go
+++ b/assert/assertion_compare.go
@@ -342,6 +342,24 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter
 	return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs)
 }
 
+// Positive asserts that the specified element is positive
+//
+//    assert.Positive(t, 1)
+//    assert.Positive(t, 1.23)
+func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
+	zero := reflect.Zero(reflect.TypeOf(e))
+	return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs)
+}
+
+// Negative asserts that the specified element is negative
+//
+//    assert.Negative(t, -1)
+//    assert.Negative(t, -1.23)
+func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
+	zero := reflect.Zero(reflect.TypeOf(e))
+	return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs)
+}
+
 func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
 	if h, ok := t.(tHelper); ok {
 		h.Helper()
diff --git a/assert/assertion_format.go b/assert/assertion_format.go
index b7a0ed5..4dfd122 100644
--- a/assert/assertion_format.go
+++ b/assert/assertion_format.go
@@ -441,6 +441,17 @@ func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args .
 	return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
 }
 
+// Negativef asserts that the specified element is negative
+//
+//    assert.Negativef(t, -1, "error message %s", "formatted")
+//    assert.Negativef(t, -1.23, "error message %s", "formatted")
+func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	return Negative(t, e, append([]interface{}{msg}, args...)...)
+}
+
 // Neverf asserts that the given condition doesn't satisfy in waitFor time,
 // periodically checking the target function each tick.
 //
@@ -647,6 +658,17 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str
 	return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
 }
 
+// Positivef asserts that the specified element is positive
+//
+//    assert.Positivef(t, 1, "error message %s", "formatted")
+//    assert.Positivef(t, 1.23, "error message %s", "formatted")
+func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	return Positive(t, e, append([]interface{}{msg}, args...)...)
+}
+
 // Regexpf asserts that a specified regexp matches a string.
 //
 //  assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go
index 60ba9b0..25337a6 100644
--- a/assert/assertion_forward.go
+++ b/assert/assertion_forward.go
@@ -871,6 +871,28 @@ func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...i
 	return Lessf(a.t, e1, e2, msg, args...)
 }
 
+// Negative asserts that the specified element is negative
+//
+//    a.Negative(-1)
+//    a.Negative(-1.23)
+func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) bool {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	return Negative(a.t, e, msgAndArgs...)
+}
+
+// Negativef asserts that the specified element is negative
+//
+//    a.Negativef(-1, "error message %s", "formatted")
+//    a.Negativef(-1.23, "error message %s", "formatted")
+func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) bool {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	return Negativef(a.t, e, msg, args...)
+}
+
 // Never asserts that the given condition doesn't satisfy in waitFor time,
 // periodically checking the target function each tick.
 //
@@ -1283,6 +1305,28 @@ func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) b
 	return Panicsf(a.t, f, msg, args...)
 }
 
+// Positive asserts that the specified element is positive
+//
+//    a.Positive(1)
+//    a.Positive(1.23)
+func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) bool {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	return Positive(a.t, e, msgAndArgs...)
+}
+
+// Positivef asserts that the specified element is positive
+//
+//    a.Positivef(1, "error message %s", "formatted")
+//    a.Positivef(1.23, "error message %s", "formatted")
+func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) bool {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	return Positivef(a.t, e, msg, args...)
+}
+
 // Regexp asserts that a specified regexp matches a string.
 //
 //  a.Regexp(regexp.MustCompile("start"), "it's starting")
diff --git a/require/require.go b/require/require.go
index 9ad171a..51820df 100644
--- a/require/require.go
+++ b/require/require.go
@@ -1112,6 +1112,34 @@ func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...inter
 	t.FailNow()
 }
 
+// Negative asserts that the specified element is negative
+//
+//    assert.Negative(t, -1)
+//    assert.Negative(t, -1.23)
+func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	if assert.Negative(t, e, msgAndArgs...) {
+		return
+	}
+	t.FailNow()
+}
+
+// Negativef asserts that the specified element is negative
+//
+//    assert.Negativef(t, -1, "error message %s", "formatted")
+//    assert.Negativef(t, -1.23, "error message %s", "formatted")
+func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	if assert.Negativef(t, e, msg, args...) {
+		return
+	}
+	t.FailNow()
+}
+
 // Never asserts that the given condition doesn't satisfy in waitFor time,
 // periodically checking the target function each tick.
 //
@@ -1638,6 +1666,34 @@ func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}
 	t.FailNow()
 }
 
+// Positive asserts that the specified element is positive
+//
+//    assert.Positive(t, 1)
+//    assert.Positive(t, 1.23)
+func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	if assert.Positive(t, e, msgAndArgs...) {
+		return
+	}
+	t.FailNow()
+}
+
+// Positivef asserts that the specified element is positive
+//
+//    assert.Positivef(t, 1, "error message %s", "formatted")
+//    assert.Positivef(t, 1.23, "error message %s", "formatted")
+func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	if assert.Positivef(t, e, msg, args...) {
+		return
+	}
+	t.FailNow()
+}
+
 // Regexp asserts that a specified regexp matches a string.
 //
 //  assert.Regexp(t, regexp.MustCompile("start"), "it's starting")
diff --git a/require/require_forward.go b/require/require_forward.go
index aa71049..ed54a9d 100644
--- a/require/require_forward.go
+++ b/require/require_forward.go
@@ -872,6 +872,28 @@ func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...i
 	Lessf(a.t, e1, e2, msg, args...)
 }
 
+// Negative asserts that the specified element is negative
+//
+//    a.Negative(-1)
+//    a.Negative(-1.23)
+func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	Negative(a.t, e, msgAndArgs...)
+}
+
+// Negativef asserts that the specified element is negative
+//
+//    a.Negativef(-1, "error message %s", "formatted")
+//    a.Negativef(-1.23, "error message %s", "formatted")
+func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	Negativef(a.t, e, msg, args...)
+}
+
 // Never asserts that the given condition doesn't satisfy in waitFor time,
 // periodically checking the target function each tick.
 //
@@ -1284,6 +1306,28 @@ func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interfa
 	Panicsf(a.t, f, msg, args...)
 }
 
+// Positive asserts that the specified element is positive
+//
+//    a.Positive(1)
+//    a.Positive(1.23)
+func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	Positive(a.t, e, msgAndArgs...)
+}
+
+// Positivef asserts that the specified element is positive
+//
+//    a.Positivef(1, "error message %s", "formatted")
+//    a.Positivef(1.23, "error message %s", "formatted")
+func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) {
+	if h, ok := a.t.(tHelper); ok {
+		h.Helper()
+	}
+	Positivef(a.t, e, msg, args...)
+}
+
 // Regexp asserts that a specified regexp matches a string.
 //
 //  a.Regexp(regexp.MustCompile("start"), "it's starting")


Test patch:
diff --git a/assert/assertion_compare_test.go b/assert/assertion_compare_test.go
index 4f7f441..b4631a4 100644
--- a/assert/assertion_compare_test.go
+++ b/assert/assertion_compare_test.go
@@ -251,6 +251,82 @@ func TestLessOrEqual(t *testing.T) {
 	}
 }
 
+func TestPositive(t *testing.T) {
+	mockT := new(testing.T)
+
+	if !Positive(mockT, 1) {
+		t.Error("Positive should return true")
+	}
+
+	if !Positive(mockT, 1.23) {
+		t.Error("Positive should return true")
+	}
+
+	if Positive(mockT, -1) {
+		t.Error("Positive should return false")
+	}
+
+	if Positive(mockT, -1.23) {
+		t.Error("Positive should return false")
+	}
+
+	// Check error report
+	for _, currCase := range []struct {
+		e   interface{}
+		msg string
+	}{
+		{e: int(-1), msg: `"-1" is not positive`},
+		{e: int8(-1), msg: `"-1" is not positive`},
+		{e: int16(-1), msg: `"-1" is not positive`},
+		{e: int32(-1), msg: `"-1" is not positive`},
+		{e: int64(-1), msg: `"-1" is not positive`},
+		{e: float32(-1.23), msg: `"-1.23" is not positive`},
+		{e: float64(-1.23), msg: `"-1.23" is not positive`},
+	} {
+		out := &outputT{buf: bytes.NewBuffer(nil)}
+		False(t, Positive(out, currCase.e))
+		Contains(t, string(out.buf.Bytes()), currCase.msg)
+	}
+}
+
+func TestNegative(t *testing.T) {
+	mockT := new(testing.T)
+
+	if !Negative(mockT, -1) {
+		t.Error("Negative should return true")
+	}
+
+	if !Negative(mockT, -1.23) {
+		t.Error("Negative should return true")
+	}
+
+	if Negative(mockT, 1) {
+		t.Error("Negative should return false")
+	}
+
+	if Negative(mockT, 1.23) {
+		t.Error("Negative should return false")
+	}
+
+	// Check error report
+	for _, currCase := range []struct {
+		e   interface{}
+		msg string
+	}{
+		{e: int(1), msg: `"1" is not negative`},
+		{e: int8(1), msg: `"1" is not negative`},
+		{e: int16(1), msg: `"1" is not negative`},
+		{e: int32(1), msg: `"1" is not negative`},
+		{e: int64(1), msg: `"1" is not negative`},
+		{e: float32(1.23), msg: `"1.23" is not negative`},
+		{e: float64(1.23), msg: `"1.23" is not negative`},
+	} {
+		out := &outputT{buf: bytes.NewBuffer(nil)}
+		False(t, Negative(out, currCase.e))
+		Contains(t, string(out.buf.Bytes()), currCase.msg)
+	}
+}
+
 func Test_compareTwoValuesDifferentValuesTypes(t *testing.T) {
 	mockT := new(testing.T)