Skip to content

Commit

Permalink
Add BeElementOf matcher
Browse files Browse the repository at this point in the history
The BeElementOf matcher checks if actual is contained within the passed in
elements. BeElementOf() always uses Equal() to perform the match.

When the passed in elements are comprised of a single one element that is
either a Map or Array or Slice, it behaves as the reverse form of the
ContainElement() with the Equal() matcher.

Otherwise, it provides syntactic sugar for:
Or(Equals(1), Equals(2), ...)
  • Loading branch information
ahadas authored and williammartin committed May 15, 2019
1 parent f0e010e commit 6a48b48
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 0 deletions.
16 changes: 16 additions & 0 deletions matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,22 @@ func ContainElement(element interface{}) types.GomegaMatcher {
}
}

//BeElementOf succeeds if actual is contained in the passed in elements.
//BeElementOf() always uses Equal() to perform the match.
//When the passed in elements are comprised of a single element that is either an Array or Slice, BeElementOf() behaves
//as the reverse of ContainElement() that operates with Equal() to perform the match.
// Expect(2).Should(BeElementOf([]int{1, 2}))
// Expect(2).Should(BeElementOf([2]int{1, 2}))
//Otherwise, BeElementOf() provides a syntactic sugar for Or(Equal(_), Equal(_), ...):
// Expect(2).Should(BeElementOf(1, 2))
//
//Actual must be typed.
func BeElementOf(elements ...interface{}) types.GomegaMatcher {
return &matchers.BeElementOfMatcher{
Elements: elements,
}
}

//ConsistOf succeeds if actual contains precisely the elements passed into the matcher. The ordering of the elements does not matter.
//By default ConsistOf() uses Equal() to match the elements, however custom matchers can be passed in instead. Here are some examples:
//
Expand Down
55 changes: 55 additions & 0 deletions matchers/be_element_of_matcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package matchers

import (
"fmt"
"reflect"

"github.com/onsi/gomega/format"
)

type BeElementOfMatcher struct {
Elements []interface{}
}

func (matcher *BeElementOfMatcher) Match(actual interface{}) (success bool, err error) {
if reflect.TypeOf(actual) == nil {
return false, fmt.Errorf("BeElement matcher expects actual to be typed")
}

length := len(matcher.Elements)
valueAt := func(i int) interface{} {
return matcher.Elements[i]
}
// Special handling of a single element of type Array or Slice
if length == 1 && isArrayOrSlice(valueAt(0)) {
element := valueAt(0)
value := reflect.ValueOf(element)
length = value.Len()
valueAt = func(i int) interface{} {
return value.Index(i).Interface()
}
}

var lastError error
for i := 0; i < length; i++ {
matcher := &EqualMatcher{Expected: valueAt(i)}
success, err := matcher.Match(actual)
if err != nil {
lastError = err
continue
}
if success {
return true, nil
}
}

return false, lastError
}

func (matcher *BeElementOfMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to be an element of", matcher.Elements)
}

func (matcher *BeElementOfMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to be an element of", matcher.Elements)
}
56 changes: 56 additions & 0 deletions matchers/be_element_of_matcher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package matchers_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/matchers"
)

var _ = Describe("BeElementOf", func() {
Context("when passed a supported type", func() {
It("should do the right thing", func() {
Expect(2).Should(BeElementOf([2]int{1, 2}))
Expect(3).ShouldNot(BeElementOf([2]int{1, 2}))

Expect(2).Should(BeElementOf([]int{1, 2}))
Expect(3).ShouldNot(BeElementOf([]int{1, 2}))

Expect(2).Should(BeElementOf(1, 2))
Expect(3).ShouldNot(BeElementOf(1, 2))

Expect("abc").Should(BeElementOf("abc"))
Expect("abc").ShouldNot(BeElementOf("def"))

Expect("abc").ShouldNot(BeElementOf())
Expect(7).ShouldNot(BeElementOf(nil))

arr := make([]myCustomType, 2)
arr[0] = myCustomType{s: "foo", n: 3, f: 2.0, arr: []string{"a", "b"}}
arr[1] = myCustomType{s: "foo", n: 3, f: 2.0, arr: []string{"a", "c"}}
Expect(myCustomType{s: "foo", n: 3, f: 2.0, arr: []string{"a", "b"}}).Should(BeElementOf(arr))
Expect(myCustomType{s: "foo", n: 3, f: 2.0, arr: []string{"b", "c"}}).ShouldNot(BeElementOf(arr))
})
})

Context("when passed a correctly typed nil", func() {
It("should operate succesfully on the passed in value", func() {
var nilSlice []int
Expect(1).ShouldNot(BeElementOf(nilSlice))

var nilMap map[int]string
Expect("foo").ShouldNot(BeElementOf(nilMap))
})
})

Context("when passed an unsupported type", func() {
It("should error", func() {
success, err := (&BeElementOfMatcher{Elements: []interface{}{0}}).Match(nil)
Expect(success).Should(BeFalse())
Expect(err).Should(HaveOccurred())

success, err = (&BeElementOfMatcher{Elements: nil}).Match(nil)
Expect(success).Should(BeFalse())
Expect(err).Should(HaveOccurred())
})
})
})

0 comments on commit 6a48b48

Please sign in to comment.