diff --git a/Changes.md b/Changes.md index f1cbee4..adc587d 100644 --- a/Changes.md +++ b/Changes.md @@ -1,5 +1,6 @@ ## 0.0.6 2021-03-27 +* Added a `d.IsNot` assertion. * Clarified that `d.ValueIs` only accepts literal values as its second argument, not a `Comparer`. diff --git a/pkg/detest/basic.go b/pkg/detest/basic.go index ce8cc4f..7538632 100644 --- a/pkg/detest/basic.go +++ b/pkg/detest/basic.go @@ -9,7 +9,8 @@ import ( "strconv" ) -// ExactEqualityComparer implements exact comparison of two values. +// ExactEqualityComparer implements exact comparison of two values, checking +// that they're equal. type ExactEqualityComparer struct { expect interface{} } @@ -52,6 +53,22 @@ func (d *D) Passes(actual interface{}, expect Comparer, args ...interface{}) boo return d.ok(argsToName(args, "unnamed d.Passes call")) } +// IsNot tests that two variables are not exactly equal. The first variable is +// the actual variable and the second is what is expected. The `expect` must +// be a literal value. +// +// The final arguments follow the same rules as `d.Is`. +// +// Under the hood this is implemented with the ExactInequalityComparer. +func (d *D) IsNot(actual, expect interface{}, args ...interface{}) bool { + d.ResetState() + d.PushActual(actual) + defer d.PopActual() + + d.NotEqual(expect).Compare(d) + return d.ok(argsToName(args, "unnamed d.IsNot call")) +} + // Require takes a boolean and calls t.Fatal if it's false. The typical use is // to write something like: // @@ -112,6 +129,49 @@ func (eec ExactEqualityComparer) Compare(d *D) { d.AddResult(result) } +// ExactInequalityComparer implements exact comparison of two values, checking +// that they're not equal. +type ExactInequalityComparer struct { + expect interface{} +} + +// NotEqual takes an expected literal value and returns an +// ExactInequalityComparer for later use. +func (d *D) NotEqual(expect interface{}) ExactInequalityComparer { + return ExactInequalityComparer{expect} +} + +// Compare compares the value in d.Actual() to the expected value passed to +// Equal(). +func (eic ExactInequalityComparer) Compare(d *D) { + actual := d.Actual() + actualType := reflect.TypeOf(actual) + d.PushPath(d.NewPath(describeType(actualType), 1, "detest.(*D).NotEqual")) + defer d.PopPath() + + expect := eic.expect + result := result{ + actual: newValue(actual), + expect: newValue(expect), + op: "==", + } + + expectType := reflect.TypeOf(expect) + if actualType == expectType { + result.pass = !exactCompare(actual, expect) + if !result.pass { + result.where = inValue + } + } else { + result.pass = !nilValuesAreEqual(actual, expect) + if result.pass { + result.where = inType + } + } + + d.AddResult(result) +} + func nilValuesAreEqual(actual, expect interface{}) bool { actualValue := reflect.ValueOf(actual) expectValue := reflect.ValueOf(expect) diff --git a/pkg/detest/basic_test.go b/pkg/detest/basic_test.go index 48dba41..a698606 100644 --- a/pkg/detest/basic_test.go +++ b/pkg/detest/basic_test.go @@ -28,7 +28,7 @@ func TestIs(t *testing.T) { mT.AssertCalled(t, "Fail") }) - t.Run("Equivalent values do not compare as true", func(t *testing.T) { + t.Run("Equivalent values do not compare as equal", func(t *testing.T) { mT := new(mockT) d := NewWithOutput(mT, mT) d.Is(int32(1), int64(2), "int32(1) == int64(2)") @@ -59,6 +59,38 @@ func TestIs(t *testing.T) { }) } +func TestIsNot(t *testing.T) { + t.Run("Passing test", func(t *testing.T) { + mT := new(mockT) + d := NewWithOutput(mT, mT) + d.IsNot(1, 42, "1 != 42") + mT.AssertNotCalled(t, "Fail") + mT.AssertCalled(t, "WriteString", "Assertion ok: 1 != 42\n") + }) + + t.Run("Failing test", func(t *testing.T) { + mT := new(mockT) + d := NewWithOutput(mT, mT) + d.IsNot(1, 1, "1 != 1") + mT.AssertCalled(t, "Fail") + }) + + t.Run("Equivalent values do not compare as equal", func(t *testing.T) { + mT := new(mockT) + d := NewWithOutput(mT, mT) + d.IsNot(int32(1), int64(2), "int32(1) != int64(2)") + mT.AssertNotCalled(t, "Fail") + mT.AssertCalled(t, "WriteString", "Assertion ok: int32(1) != int64(2)\n") + }) + + t.Run("Can handle nil", func(t *testing.T) { + mT := new(mockT) + d := NewWithOutput(mT, mT) + d.IsNot(nil, nil, "nil != nil") + mT.AssertCalled(t, "Fail") + }) +} + // Go has this (so great) thing where there are different types of nils. A // bare `nil` is not the same as a nil which comes from a types but // uninitialized variable. But they should compare the same. Previously detest @@ -95,6 +127,20 @@ func TestIsNilTypeHandling(t *testing.T) { mT.AssertCalled(t, "WriteString", "Assertion ok: nil == uninit\n") }) + t.Run(fmt.Sprintf("IsNot(%s == nil)", desc), func(t *testing.T) { + mT := new(mockT) + d := NewWithOutput(mT, mT) + + d.IsNot(v, nil, "uninit != nil") + mT.AssertCalled(t, "Fail") + }) + t.Run(fmt.Sprintf("IsNot(nil == %s)", desc), func(t *testing.T) { + mT := new(mockT) + d := NewWithOutput(mT, mT) + d.IsNot(nil, v, "nil != uninit") + mT.AssertCalled(t, "Fail") + }) + t.Run(fmt.Sprintf("ValueIs(%s == nil)", desc), func(t *testing.T) { mT := new(mockT) d := NewWithOutput(mT, mT) @@ -120,13 +166,19 @@ func TestIsNilTypeHandling(t *testing.T) { for _, v2 := range []interface{}{sl, ma, f, p, up, c} { desc2 := describe(v2) if desc != desc2 { - t.Run(fmt.Sprintf("Is(%s != %s)", desc, desc2), func(t *testing.T) { + t.Run(fmt.Sprintf("Is(%s == %s)", desc, desc2), func(t *testing.T) { mT := new(mockT) d := NewWithOutput(mT, mT) d.Is(v, v2, "nil == uninit") mT.AssertCalled(t, "Fail") }) - t.Run(fmt.Sprintf("ValueIs(%s != %s)", desc, desc2), func(t *testing.T) { + t.Run(fmt.Sprintf("IsNot(%s == %s)", desc, desc2), func(t *testing.T) { + mT := new(mockT) + d := NewWithOutput(mT, mT) + d.IsNot(v, v2, "nil != uninit") + mT.AssertNotCalled(t, "Fail") + }) + t.Run(fmt.Sprintf("ValueIs(%s == %s)", desc, desc2), func(t *testing.T) { mT := new(mockT) d := NewWithOutput(mT, mT) d.ValueIs(v, v2, "nil == uninit")