Repository: gavv/httpexpect
Pull request #211 (2022-12-25 11:01:59)

feat(array): Implement Find and FindAll
close https://github.com/gavv/httpexpect/issues/168

coveralls: 
[![Coverage Status](https://coveralls.io/builds/55447255/badge)](https://coveralls.io/builds/55447255)

Coverage: 94.716% (-0.5%) from 95.249% when pulling **549e635636f0ff39c5bb8f74c6d1224116bbe304 on k4n4ry:feature/impl-find** into **691d097e459e4de22e51c31e07b94bbca4c16fe7 on gavv:master**.

gavv: It would be nice to cover two additional cases in unit tests:

* when predicate returns true, but assertion fails, and predicate is considered failed
* for FindAll: same, but when assertion fails only for some of the values (to ensure that assertion failure does not affect subsequent matches)
gavv: @k4n4ry BTW, welcome to discord chat https://discord.gg/5SCPCuCWA9 :)
gavv: BTW, coveralls reported reduced coverage.

Could you cover missing branches please? They are new AssertUsage failures.

https://coveralls.io/builds/55447288/source?filename=object.go#L368
https://coveralls.io/builds/55447288/source?filename=array.go#L382
k4n4ry: thanks for review, and I added test cases for object/array .

> It would be nice to cover two additional cases in unit tests:
> 
> when predicate returns true, but assertion fails, and predicate is considered failed
> for FindAll: same, but when assertion fails only for some of the values (to ensure that assertion failure does not affect subsequent matches)

and added test cases for missing branches.

> Could you cover missing branches please? They are new AssertUsage failures.
> 
> https://coveralls.io/builds/55447288/source?filename=object.go#L368
> https://coveralls.io/builds/55447288/source?filename=array.go#L382


Patch:
diff --git a/array.go b/array.go
index f044935..99621ad 100644
--- a/array.go
+++ b/array.go
@@ -372,6 +372,179 @@ func (a *Array) Transform(fn func(index int, value interface{}) interface{}) *Ar
 	return newArray(a.chain, array)
 }
 
+// Find accepts a function that returns a boolean. The function is ran
+// over the array items. If the function returns true, the item
+// is returned as value. If the function returns false or assertion
+// inside it fails, the item is skipped. If all the items are skipped,
+// then Find fails and returns empty (non-nil) value.
+//
+// If there are any failed assertions in the filtering function, the
+// item is omitted without causing test failure.
+//
+// Example:
+//
+//	array := NewArray(t, []interface{}{1, "foo", 2, "bar"})
+//	foundValue := array.Find(func(index int, value *httpexpect.Value) bool {
+//		value.String().NotEmpty()		// fails on 1 and 2
+//		return value.Raw() != "bar"		// fails on "bar"
+//	})
+//	foundValue.Equal("foo")	// succeeds
+func (a *Array) Find(fn func(index int, value *Value) bool) *Value {
+	a.chain.enter("Find()")
+	defer a.chain.leave()
+
+	if a.chain.failed() {
+		return newValue(a.chain, nil)
+	}
+
+	if fn == nil {
+		a.chain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected nil function argument"),
+			},
+		})
+		return newValue(a.chain, nil)
+	}
+
+	for index, element := range a.value {
+		valueChain := a.chain.clone()
+		valueChain.setSeverity(SeverityLog)
+		chainFailed := false
+		valueChain.setFailCallback(func() {
+			chainFailed = true
+		})
+		valueChain.replace("Find[%v]", index)
+		if fn(index, newValue(valueChain, element)) && !chainFailed {
+			return newValue(a.chain, element)
+		}
+	}
+
+	a.chain.fail(AssertionFailure{
+		Type:   AssertValid,
+		Actual: &AssertionValue{a.value},
+		Errors: []error{
+			errors.New("expected: at least one array element matched predicate"),
+		},
+	})
+
+	return newValue(a.chain, nil)
+}
+
+// FindAll accepts a function that returns a boolean. The function is ran
+// over the array items. If the function returns true, the item is added
+// to the slice of values. If the function returns false or assertion
+// inside it fails, the item is skipped. After iterating through all the
+// items of the array, slice of values are returned.
+//
+// If there are any failed assertions in the filtering function, the
+// item is omitted without causing test failure.
+//
+// Example:
+//
+//	array := NewArray(t, []interface{}{1, "foo", 2, "bar", "baz"})
+//	foundValues := array.FindAll(func(index int, value *httpexpect.Value) bool {
+//		value.String().NotEmpty()		// fails on 1 and 2
+//		return value.Raw() != "bar"		// fails on "bar"
+//	})
+//
+//	assert.Equal(t, len(foundValues), 2)
+//	foundValues[0].Equal("foo")
+//	foundValues[1].Equal("baz")
+func (a *Array) FindAll(fn func(index int, value *Value) bool) []Value {
+	a.chain.enter("FindAll()")
+	defer a.chain.leave()
+
+	if a.chain.failed() {
+		return []Value{}
+	}
+
+	if fn == nil {
+		a.chain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected nil function argument"),
+			},
+		})
+		return []Value{}
+	}
+
+	foundValues := make([]Value, 0, len(a.value))
+	for index, element := range a.value {
+		valueChain := a.chain.clone()
+		valueChain.setSeverity(SeverityLog)
+		chainFailed := false
+		valueChain.setFailCallback(func() {
+			chainFailed = true
+		})
+		valueChain.replace("FindAll[%v]", index)
+		if fn(index, newValue(valueChain, element)) && !chainFailed {
+			foundValues = append(foundValues, *newValue(a.chain, element))
+		}
+	}
+
+	return foundValues
+}
+
+// NotFind accepts a function that returns a boolean. The function is ran
+// over the array items. If the function returns true, then NotFind fails
+// and returns empty (non-nil) array. If the function returns false or
+// assertion inside it fails in all iteration, NotFind succeeds and
+// returns original array.
+//
+// If there are any failed assertions in the filtering function, the
+// item is omitted without causing test failure.
+//
+// Example:
+//
+//	array := NewArray(t, []interface{}{1, "foo", 2, "bar"})
+//	afterArray := array.NotFind(func(index int, value *httpexpect.Value) bool {
+//		value.String().NotEmpty()		// fails on 1 and 2
+//		return value.Raw() == "baz"		// fails on "foo" and "bar"
+//	})
+//	afterArray.Equal([]interface{}{1, "foo", 2, "bar"})	// succeeds
+func (a *Array) NotFind(fn func(index int, value *Value) bool) *Array {
+	a.chain.enter("NotFind()")
+	defer a.chain.leave()
+
+	if a.chain.failed() {
+		return newArray(a.chain, nil)
+	}
+
+	if fn == nil {
+		a.chain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected nil function argument"),
+			},
+		})
+		return newArray(a.chain, nil)
+	}
+
+	for index, element := range a.value {
+		valueChain := a.chain.clone()
+		valueChain.setSeverity(SeverityLog)
+		chainFailed := false
+		valueChain.setFailCallback(func() {
+			chainFailed = true
+		})
+		valueChain.replace("NotFind[%v]", index)
+		if fn(index, newValue(valueChain, element)) && !chainFailed {
+			a.chain.fail(AssertionFailure{
+				Type:     AssertNotContainsElement,
+				Expected: &AssertionValue{element},
+				Actual:   &AssertionValue{a.value},
+				Errors: []error{
+					errors.New("expected: none of the array elements match predicate"),
+				},
+			})
+			return newArray(a.chain, nil)
+		}
+	}
+
+	return a
+}
+
 // Empty succeeds if array is empty.
 //
 // Example:
diff --git a/object.go b/object.go
index 863d39a..fa58d52 100644
--- a/object.go
+++ b/object.go
@@ -354,6 +354,230 @@ func (o *Object) Transform(fn func(key string, value interface{}) interface{}) *
 	return newObject(o.chain, object)
 }
 
+// Find accepts a function that returns a boolean. The function is ran
+// over the object items. If the function returns true, the item
+// is returned as value. If the function returns false or assertion
+// inside it fails, the item is skipped. If all the items are skipped,
+// then Find fails and returns empty (non-nil) value.
+//
+// If there are any failed assertions in the filtering function, the
+// item is omitted without causing test failure.
+//
+// Example:
+//
+//	object := NewObject(t, map[string]interface{}{
+//		"foo": "bar",
+//		"baz": 6,
+//		"qux": "quux",
+//	})
+//	foundValue := object.Find(func(key string, value *httpexpect.Value)  bool {
+//		value.String().NotEmpty()	// fails on 6
+//		return key == "qux"		// fails on "bar"
+//	})
+//	foundValue.Equal("quux")	// succeeds
+func (o *Object) Find(fn func(key string, value *Value) bool) *Value {
+	o.chain.enter("Find()")
+	defer o.chain.leave()
+
+	if o.chain.failed() {
+		return newValue(o.chain, nil)
+	}
+
+	if fn == nil {
+		o.chain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected nil function argument"),
+			},
+		})
+		return newValue(o.chain, nil)
+	}
+
+	type kv struct {
+		key   string
+		value interface{}
+	}
+	kvs := make([]kv, 0, len(o.value))
+	for key, element := range o.value {
+		kvs = append(kvs, kv{key: key, value: element})
+	}
+	sort.Slice(kvs, func(i, j int) bool { return kvs[i].key < kvs[j].key })
+
+	for _, kv := range kvs {
+		key, element := kv.key, kv.value
+		valueChain := o.chain.clone()
+		valueChain.setSeverity(SeverityLog)
+		chainFailed := false
+		valueChain.setFailCallback(func() {
+			chainFailed = true
+		})
+		valueChain.replace("Find[%q]", key)
+		if fn(key, newValue(valueChain, element)) && !chainFailed {
+			return newValue(o.chain, element)
+		}
+	}
+
+	o.chain.fail(AssertionFailure{
+		Type:   AssertValid,
+		Actual: &AssertionValue{o.value},
+		Errors: []error{
+			errors.New("expected: at least one object element matched predicate"),
+		},
+	})
+
+	return newValue(o.chain, nil)
+}
+
+// FindAll accepts a function that returns a boolean. The function is ran
+// over the object items. If the function returns true, the item is added
+// to the slice of values. If the function returns false or assertion
+// inside it fails, the item is skipped. After iterating through all the
+// items of the object, slice of values are returned.
+//
+// If there are any failed assertions in the filtering function, the
+// item is omitted without causing test failure.
+//
+// Example:
+//
+//	object := NewObject(t, map[string]interface{}{
+//		"foo": "bar",
+//		"baz": 6,
+//		"qux": "quux",
+//		"corge": "grault",
+//	})
+//	foundValues := object.FindAll(func(key string, value *httpexpect.Value) bool {
+//		value.String().NotEmpty()	// fails on 6
+//		return key != "qux"		// fails on "quux"
+//	})
+//
+//	assert.Equal(t, len(foundValues), 2)
+//	foundValues[0].Equal("grault")	// foundValues are sorted by key of object
+//	foundValues[1].Equal("bar")
+func (o *Object) FindAll(fn func(key string, value *Value) bool) []Value {
+	o.chain.enter("FindAll()")
+	defer o.chain.leave()
+
+	if o.chain.failed() {
+		return []Value{}
+	}
+
+	if fn == nil {
+		o.chain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected nil function argument"),
+			},
+		})
+		return []Value{}
+	}
+
+	type kv struct {
+		key   string
+		value interface{}
+	}
+	kvs := make([]kv, 0, len(o.value))
+	for key, element := range o.value {
+		kvs = append(kvs, kv{key: key, value: element})
+	}
+	sort.Slice(kvs, func(i, j int) bool { return kvs[i].key < kvs[j].key })
+
+	foundValues := make([]Value, 0, len(o.value))
+
+	for _, kv := range kvs {
+		key, element := kv.key, kv.value
+		valueChain := o.chain.clone()
+		valueChain.setSeverity(SeverityLog)
+		chainFailed := false
+		valueChain.setFailCallback(func() {
+			chainFailed = true
+		})
+		valueChain.replace("FindAll[%q]", key)
+		if fn(key, newValue(valueChain, element)) && !chainFailed {
+			foundValues = append(foundValues, *newValue(o.chain, element))
+		}
+	}
+
+	return foundValues
+}
+
+// NotFind accepts a function that returns a boolean. The function is ran
+// over the object items. If the function returns true, then NotFind fails
+// and returns empty (non-nil) object. If the function returns false or
+// assertion inside it fails in all iteration, NotFind succeeds and
+// returns original object.
+//
+// If there are any failed assertions in the filtering function, the
+// item is omitted without causing test failure.
+//
+// Example:
+//
+//	object := NewObject(t, map[string]interface{}{
+//		"foo": "bar",
+//		"baz": 6,
+//		"qux": "quux",
+//	})
+//	afterObject := object.NotFind(func(key string, value *httpexpect.Value)  bool {
+//		value.String().NotEmpty()	// fails on 6
+//		return key == "corge"		// fails on "bar" and "quux"
+//	})
+//	afterObject.Equal(map[string]interface{}{
+//		"foo": "bar",
+//		"baz": 6,
+//		"qux": "quux",
+//	})	// succeeds
+func (o *Object) NotFind(fn func(key string, value *Value) bool) *Object {
+	o.chain.enter("NotFind()")
+	defer o.chain.leave()
+
+	if o.chain.failed() {
+		return newObject(o.chain, nil)
+	}
+
+	if fn == nil {
+		o.chain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected nil function argument"),
+			},
+		})
+		return newObject(o.chain, nil)
+	}
+
+	type kv struct {
+		key   string
+		value interface{}
+	}
+	kvs := make([]kv, 0, len(o.value))
+	for key, element := range o.value {
+		kvs = append(kvs, kv{key: key, value: element})
+	}
+	sort.Slice(kvs, func(i, j int) bool { return kvs[i].key < kvs[j].key })
+
+	for _, kv := range kvs {
+		key, element := kv.key, kv.value
+		valueChain := o.chain.clone()
+		valueChain.setSeverity(SeverityLog)
+		chainFailed := false
+		valueChain.setFailCallback(func() {
+			chainFailed = true
+		})
+		valueChain.replace("NotFind[%q]", key)
+		if fn(key, newValue(valueChain, element)) && !chainFailed {
+			o.chain.fail(AssertionFailure{
+				Type:     AssertNotContainsElement,
+				Expected: &AssertionValue{element},
+				Actual:   &AssertionValue{o.value},
+				Errors: []error{
+					errors.New("expected: none of the array elements match predicate"),
+				},
+			})
+			return newObject(o.chain, nil)
+		}
+	}
+
+	return o
+}
+
 // Empty succeeds if object is empty.
 //
 // Example:


Test patch:
diff --git a/array_test.go b/array_test.go
index 902844e..1cec64c 100644
--- a/array_test.go
+++ b/array_test.go
@@ -47,6 +47,18 @@ func TestArrayFailed(t *testing.T) {
 		value.Transform(func(index int, value interface{}) interface{} {
 			return nil
 		})
+		value.Find(func(index int, value *Value) bool {
+			value.String().NotEmpty()
+			return true
+		})
+		value.FindAll(func(index int, value *Value) bool {
+			value.String().NotEmpty()
+			return true
+		})
+		value.NotFind(func(index int, value *Value) bool {
+			value.String().NotEmpty()
+			return true
+		})
 	}
 
 	t.Run("failed_chain", func(t *testing.T) {
@@ -952,3 +964,311 @@ func TestArrayFilter(t *testing.T) {
 			filteredArray.chain.assertNotFailed(t)
 		})
 }
+
+func TestArrayFind(t *testing.T) {
+	t.Run("Find value in array of the same type", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{1, 2, 3, 4, 5, 6})
+		foundValue := array.Find(func(index int, value *Value) bool {
+			return value.Raw() == 2.0
+		})
+		assert.Equal(t, 2.0, foundValue.Raw())
+		assert.Equal(t, array.Raw(), []interface{}{1.0, 2.0, 3.0, 4.0, 5.0, 6.0})
+
+		array.chain.assertNotFailed(t)
+		foundValue.chain.assertNotFailed(t)
+	})
+
+	t.Run("Find value in arraly of the multi types", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{1, "foo", true, "bar"})
+		foundValue := array.Find(func(index int, value *Value) bool {
+			stringifiedValue := value.String().NotEmpty().Raw()
+			return stringifiedValue == "bar"
+		})
+		assert.Equal(t, "bar", foundValue.Raw())
+		assert.Equal(t, array.Raw(), []interface{}{1.0, "foo", true, "bar"})
+
+		array.chain.assertNotFailed(t)
+		foundValue.chain.assertNotFailed(t)
+	})
+
+	t.Run("Find first match element", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{1, "foo", true, "bar"})
+		foundValue := array.Find(func(index int, value *Value) bool {
+			stringifiedValue := value.String().Raw()
+			return stringifiedValue != ""
+		})
+		assert.Equal(t, "foo", foundValue.Raw())
+		assert.Equal(t, array.Raw(), []interface{}{1.0, "foo", true, "bar"})
+
+		array.chain.assertNotFailed(t)
+		foundValue.chain.assertNotFailed(t)
+	})
+
+	t.Run("No match", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{1, "foo", true, "bar"})
+		foundValue := array.Find(func(index int, value *Value) bool {
+			return value.Raw() == 2.0
+		})
+		assert.Equal(t, nil, foundValue.Raw())
+		assert.Equal(t, array.Raw(), []interface{}{1.0, "foo", true, "bar"})
+
+		array.chain.assertFailed(t)
+		foundValue.chain.assertFailed(t)
+	})
+
+	t.Run("Empty array", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{})
+		foundValue := array.Find(func(index int, value *Value) bool {
+			return value.Raw() == 2.0
+		})
+		assert.Equal(t, nil, foundValue.Raw())
+		assert.Equal(t, array.Raw(), []interface{}{})
+
+		array.chain.assertFailed(t)
+		foundValue.chain.assertFailed(t)
+	})
+
+	t.Run("When predicate returns true, but assertion fails, predicate is failed",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			array := NewArray(reporter, []interface{}{1, 2})
+			foundValue := array.Find(func(index int, value *Value) bool {
+				value.String().Raw()
+				return true
+			})
+			assert.Equal(t, nil, foundValue.Raw())
+			assert.Equal(t, array.Raw(), []interface{}{1.0, 2.0})
+
+			array.chain.assertFailed(t)
+			foundValue.chain.assertFailed(t)
+		})
+
+	t.Run("Predicate func is nil", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{1, 2})
+		foundValue := array.Find(nil)
+		assert.Equal(t, nil, foundValue.Raw())
+		assert.Equal(t, array.Raw(), []interface{}{1.0, 2.0})
+
+		array.chain.assertFailed(t)
+		foundValue.chain.assertFailed(t)
+	})
+
+}
+
+func TestArrayFindAll(t *testing.T) {
+	t.Run("Find values in array of the same type", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{1, 2, 3, 4, 5, 6})
+		foundValues := array.FindAll(func(index int, value *Value) bool {
+			return value.Raw() == 2.0 || value.Raw() == 5.0
+		})
+
+		actual := []interface{}{}
+		for _, value := range foundValues {
+			actual = append(actual, value.Raw())
+		}
+
+		assert.Equal(t, []interface{}{2.0, 5.0}, actual)
+		assert.Equal(t, array.Raw(), []interface{}{1.0, 2.0, 3.0, 4.0, 5.0, 6.0})
+
+		array.chain.assertNotFailed(t)
+		for _, value := range foundValues {
+			value.chain.assertNotFailed(t)
+		}
+	})
+
+	t.Run("Find values in array of the multi types", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{1.0, "foo", true, "bar"})
+		foundValues := array.FindAll(func(index int, value *Value) bool {
+			stringifiedValue := value.String().Raw()
+			return stringifiedValue != ""
+		})
+
+		actual := []interface{}{}
+		for _, value := range foundValues {
+			actual = append(actual, value.Raw())
+		}
+		assert.Equal(t, []interface{}{"foo", "bar"}, actual)
+		assert.Equal(t, array.Raw(), []interface{}{1.0, "foo", true, "bar"})
+
+		array.chain.assertNotFailed(t)
+		for _, value := range foundValues {
+			value.chain.assertNotFailed(t)
+		}
+	})
+
+	t.Run("No match", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{1.0, "foo", true, "bar"})
+		foundValues := array.FindAll(func(index int, value *Value) bool {
+			return value.Raw() == 2.0
+		})
+
+		actual := []interface{}{}
+		for _, value := range foundValues {
+			actual = append(actual, value.Raw())
+		}
+		assert.Equal(t, []interface{}{}, actual)
+		assert.Equal(t, array.Raw(), []interface{}{1.0, "foo", true, "bar"})
+
+		array.chain.assertNotFailed(t)
+		for _, value := range foundValues {
+			value.chain.assertNotFailed(t)
+		}
+	})
+
+	t.Run("Empty array", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{})
+		foundValues := array.FindAll(func(index int, value *Value) bool {
+			return value.Raw() == 2.0
+		})
+
+		actual := []interface{}{}
+		for _, value := range foundValues {
+			actual = append(actual, value.Raw())
+		}
+		assert.Equal(t, []interface{}{}, actual)
+		assert.Equal(t, array.Raw(), []interface{}{})
+
+		array.chain.assertNotFailed(t)
+		for _, value := range foundValues {
+			value.chain.assertNotFailed(t)
+		}
+	})
+
+	t.Run("When predicate returns true, but assertion fails, predicate is failed",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			array := NewArray(reporter, []interface{}{1, 2})
+			foundValues := array.FindAll(func(index int, value *Value) bool {
+				value.String().Raw()
+				return true
+			})
+
+			actual := []interface{}{}
+			for _, value := range foundValues {
+				actual = append(actual, value.Raw())
+			}
+			assert.Equal(t, []interface{}{}, actual)
+			assert.Equal(t, array.Raw(), []interface{}{1.0, 2.0})
+
+			array.chain.assertNotFailed(t)
+			for _, value := range foundValues {
+				value.chain.assertNotFailed(t)
+			}
+		})
+
+	t.Run("Assertion failure does not affect subsequent matches", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{"foo", 1, 2, "bar"})
+		foundValues := array.FindAll(func(index int, value *Value) bool {
+			value.String().Raw()
+			return true
+		})
+
+		actual := []interface{}{}
+		for _, value := range foundValues {
+			actual = append(actual, value.Raw())
+		}
+		assert.Equal(t, []interface{}{"foo", "bar"}, actual)
+		assert.Equal(t, array.Raw(), []interface{}{"foo", 1.0, 2.0, "bar"})
+
+		array.chain.assertNotFailed(t)
+		for _, value := range foundValues {
+			value.chain.assertNotFailed(t)
+		}
+	})
+
+	t.Run("Predicate func is nil", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{1, 2})
+		foundValues := array.FindAll(nil)
+
+		actual := []interface{}{}
+		for _, value := range foundValues {
+			actual = append(actual, value.Raw())
+		}
+		assert.Equal(t, []interface{}{}, actual)
+		assert.Equal(t, array.Raw(), []interface{}{1.0, 2.0})
+
+		array.chain.assertFailed(t)
+		for _, value := range foundValues {
+			value.chain.assertFailed(t)
+		}
+	})
+}
+
+func TestArrayNotFind(t *testing.T) {
+	t.Run("Succeeds if no element matched predicate", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{1, "foo", true, "bar"})
+		afterArray := array.NotFind(func(index int, value *Value) bool {
+			return value.String().Raw() == "baz"
+		})
+		assert.Equal(t, []interface{}{1.0, "foo", true, "bar"}, afterArray.Raw())
+		assert.Equal(t, array.Raw(), []interface{}{1.0, "foo", true, "bar"})
+
+		array.chain.assertNotFailed(t)
+		afterArray.chain.assertNotFailed(t)
+	})
+
+	t.Run("Fails if there is a match", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{1, "foo", true, "bar"})
+		afterArray := array.NotFind(func(index int, value *Value) bool {
+			return value.String().NotEmpty().Raw() == "bar"
+		})
+		assert.Equal(t, []interface{}(nil), afterArray.Raw())
+		assert.Equal(t, array.Raw(), []interface{}{1.0, "foo", true, "bar"})
+
+		array.chain.assertFailed(t)
+		afterArray.chain.assertFailed(t)
+	})
+
+	t.Run("Empty array", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{})
+		foundValue := array.NotFind(func(index int, value *Value) bool {
+			return value.Raw() == 2.0
+		})
+		assert.Equal(t, []interface{}{}, foundValue.Raw())
+		assert.Equal(t, array.Raw(), []interface{}{})
+
+		array.chain.assertNotFailed(t)
+		foundValue.chain.assertNotFailed(t)
+	})
+
+	t.Run("When predicate returns true, but assertion fails, predicate is failed",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			array := NewArray(reporter, []interface{}{1, 2})
+			afterArray := array.NotFind(func(index int, value *Value) bool {
+				value.String().Raw()
+				return true
+			})
+			assert.Equal(t, []interface{}{1.0, 2.0}, afterArray.Raw())
+			assert.Equal(t, array.Raw(), []interface{}{1.0, 2.0})
+
+			array.chain.assertNotFailed(t)
+			afterArray.chain.assertNotFailed(t)
+		})
+
+	t.Run("Predicate func is nil", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{1, 2})
+		afterArray := array.NotFind(nil)
+		assert.Equal(t, []interface{}(nil), afterArray.Raw())
+		assert.Equal(t, array.Raw(), []interface{}{1.0, 2.0})
+
+		array.chain.assertFailed(t)
+		afterArray.chain.assertFailed(t)
+	})
+}
diff --git a/object_test.go b/object_test.go
index b407263..ea2a825 100644
--- a/object_test.go
+++ b/object_test.go
@@ -40,6 +40,18 @@ func TestObjectFailed(t *testing.T) {
 			value.String().NotEmpty()
 			return true
 		})
+		value.Find(func(key string, value *Value) bool {
+			value.String().NotEmpty()
+			return true
+		})
+		value.FindAll(func(key string, value *Value) bool {
+			value.String().NotEmpty()
+			return true
+		})
+		value.NotFind(func(key string, value *Value) bool {
+			value.String().NotEmpty()
+			return true
+		})
 	}
 
 	t.Run("failed_chain", func(t *testing.T) {
@@ -1029,3 +1041,433 @@ func TestObjectFilter(t *testing.T) {
 			object.chain.assertNotFailed(t)
 		})
 }
+
+func TestObjectFind(t *testing.T) {
+	t.Run("Find an object of elements of the same type and validate", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{
+			"foo":  "bar",
+			"baz":  "qux",
+			"quux": "corge",
+		})
+		foundValue := object.Find(func(key string, value *Value) bool {
+			return key == "baz"
+		})
+		assert.Equal(t, "qux", foundValue.Raw())
+		assert.Equal(t, object.Raw(), map[string]interface{}{
+			"foo":  "bar",
+			"baz":  "qux",
+			"quux": "corge",
+		})
+
+		foundValue.chain.assertNotFailed(t)
+		object.chain.assertNotFailed(t)
+	})
+
+	t.Run("Find an object of elements of the multi type and validate", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{
+			"foo":  "bar",
+			"baz":  true,
+			"qux":  -1,
+			"quux": 2,
+		})
+		foundValue := object.Find(func(key string, value *Value) bool {
+			n := value.Number().Raw()
+			return n > 1
+		})
+		assert.Equal(t, 2.0, foundValue.Raw())
+		assert.Equal(t, object.Raw(), map[string]interface{}{
+			"foo":  "bar",
+			"baz":  true,
+			"qux":  -1.0,
+			"quux": 2.0,
+		})
+
+		foundValue.chain.assertNotFailed(t)
+		object.chain.assertNotFailed(t)
+	})
+
+	t.Run("No match", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{
+			"foo":  "bar",
+			"baz":  true,
+			"qux":  -1,
+			"quux": 2,
+		})
+		foundValue := object.Find(func(key string, value *Value) bool {
+			n := value.Number().Raw()
+			return n == 3
+		})
+		assert.Equal(t, nil, foundValue.Raw())
+		assert.Equal(t, object.Raw(), map[string]interface{}{
+			"foo":  "bar",
+			"baz":  true,
+			"qux":  -1.0,
+			"quux": 2.0,
+		})
+
+		foundValue.chain.assertFailed(t)
+		object.chain.assertFailed(t)
+	})
+
+	t.Run("Empty Object", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{})
+		foundValue := object.Find(func(key string, value *Value) bool {
+			n := value.Number().Raw()
+			return n == 3
+		})
+		assert.Equal(t, nil, foundValue.Raw())
+		assert.Equal(t, object.Raw(), map[string]interface{}{})
+
+		foundValue.chain.assertFailed(t)
+		object.chain.assertFailed(t)
+	})
+
+	t.Run("When predicate returns true, but assertion fails, predicate is failed",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			object := NewObject(reporter, map[string]interface{}{
+				"foo": 1,
+				"bar": 2,
+			})
+			foundValue := object.Find(func(key string, value *Value) bool {
+				value.String().Raw()
+				return true
+			})
+			assert.Equal(t, nil, foundValue.Raw())
+			assert.Equal(t, object.Raw(), map[string]interface{}{
+				"foo": 1.0,
+				"bar": 2.0,
+			})
+
+			foundValue.chain.assertFailed(t)
+			object.chain.assertFailed(t)
+		})
+
+	t.Run("Predicate func is nil", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{
+			"foo": 1,
+			"bar": 2,
+		})
+		foundValue := object.Find(nil)
+		assert.Equal(t, nil, foundValue.Raw())
+		assert.Equal(t, object.Raw(), map[string]interface{}{
+			"foo": 1.0,
+			"bar": 2.0,
+		})
+
+		foundValue.chain.assertFailed(t)
+		object.chain.assertFailed(t)
+	})
+}
+func TestObjectFindAll(t *testing.T) {
+	t.Run("Find values in array of the same type", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{
+			"foo":  "bar",
+			"baz":  "qux",
+			"quux": "corge",
+		})
+		foundValues := object.FindAll(func(key string, value *Value) bool {
+			return key == "baz" || key == "quux"
+		})
+
+		actual := []interface{}{}
+		for _, value := range foundValues {
+			actual = append(actual, value.Raw())
+		}
+
+		assert.Equal(t, []interface{}{"qux", "corge"}, actual)
+		assert.Equal(t, object.Raw(), map[string]interface{}{
+			"foo":  "bar",
+			"baz":  "qux",
+			"quux": "corge",
+		})
+
+		for _, value := range foundValues {
+			value.chain.assertNotFailed(t)
+		}
+		object.chain.assertNotFailed(t)
+	})
+
+	t.Run("Find values in array of the multi types", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{
+			"foo":   "bar",
+			"baz":   6,
+			"qux":   "quux",
+			"corge": "grault",
+		})
+		foundValues := object.FindAll(func(key string, value *Value) bool {
+			value.String().NotEmpty()
+			return key != "qux"
+		})
+
+		actual := []interface{}{}
+		for _, value := range foundValues {
+			actual = append(actual, value.Raw())
+		}
+
+		assert.Equal(t, []interface{}{"grault", "bar"}, actual)
+		assert.Equal(t, object.Raw(), map[string]interface{}{
+			"foo":   "bar",
+			"baz":   6.0,
+			"qux":   "quux",
+			"corge": "grault",
+		})
+
+		for _, value := range foundValues {
+			value.chain.assertNotFailed(t)
+		}
+		object.chain.assertNotFailed(t)
+	})
+
+	t.Run("No match", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{
+			"foo":  "bar",
+			"baz":  true,
+			"qux":  -1,
+			"quux": 2,
+		})
+		foundValues := object.FindAll(func(key string, value *Value) bool {
+			return value.Number().Raw() == 3.0
+		})
+
+		actual := []interface{}{}
+		for _, value := range foundValues {
+			actual = append(actual, value.Raw())
+		}
+
+		assert.Equal(t, []interface{}{}, actual)
+		assert.Equal(t, object.Raw(), map[string]interface{}{
+			"foo":  "bar",
+			"baz":  true,
+			"qux":  -1.0,
+			"quux": 2.0,
+		})
+
+		for _, value := range foundValues {
+			value.chain.assertNotFailed(t)
+		}
+		object.chain.assertNotFailed(t)
+	})
+
+	t.Run("Empty array", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{})
+		foundValues := object.FindAll(func(key string, value *Value) bool {
+			return value.Number().Raw() == 3.0
+		})
+
+		actual := []interface{}{}
+		for _, value := range foundValues {
+			actual = append(actual, value.Raw())
+		}
+
+		assert.Equal(t, []interface{}{}, actual)
+		assert.Equal(t, object.Raw(), map[string]interface{}{})
+
+		for _, value := range foundValues {
+			value.chain.assertNotFailed(t)
+		}
+		object.chain.assertNotFailed(t)
+	})
+
+	t.Run("When predicate returns true, but assertion fails, predicate is failed",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			object := NewObject(reporter, map[string]interface{}{
+				"foo": 1,
+				"bar": 2,
+			})
+			foundValues := object.FindAll(func(key string, value *Value) bool {
+				value.String().Raw()
+				return true
+			})
+
+			actual := []interface{}{}
+			for _, value := range foundValues {
+				actual = append(actual, value.Raw())
+			}
+
+			assert.Equal(t, []interface{}{}, actual)
+			assert.Equal(t, object.Raw(), map[string]interface{}{
+				"foo": 1.0,
+				"bar": 2.0,
+			})
+
+			for _, value := range foundValues {
+				value.chain.assertNotFailed(t)
+			}
+			object.chain.assertNotFailed(t)
+		})
+
+	t.Run("Assertion failure does not affect subsequent matches", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{
+			"foo":  "bar",
+			"baz":  1,
+			"qux":  2,
+			"quux": "corge",
+		})
+		foundValues := object.FindAll(func(key string, value *Value) bool {
+			value.String().Raw()
+			return true
+		})
+
+		actual := []interface{}{}
+		for _, value := range foundValues {
+			actual = append(actual, value.Raw())
+		}
+
+		assert.Equal(t, []interface{}{"bar", "corge"}, actual)
+		assert.Equal(t, object.Raw(), map[string]interface{}{
+			"foo":  "bar",
+			"baz":  1.0,
+			"qux":  2.0,
+			"quux": "corge",
+		})
+
+		for _, value := range foundValues {
+			value.chain.assertNotFailed(t)
+		}
+		object.chain.assertNotFailed(t)
+	})
+
+	t.Run("Predicate func is nil", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{
+			"foo": 1,
+			"bar": 2,
+		})
+		foundValues := object.FindAll(nil)
+
+		actual := []interface{}{}
+		for _, value := range foundValues {
+			actual = append(actual, value.Raw())
+		}
+
+		assert.Equal(t, []interface{}{}, actual)
+		assert.Equal(t, object.Raw(), map[string]interface{}{
+			"foo": 1.0,
+			"bar": 2.0,
+		})
+
+		for _, value := range foundValues {
+			value.chain.assertFailed(t)
+		}
+		object.chain.assertFailed(t)
+	})
+}
+
+func TestObjectNotFind(t *testing.T) {
+	t.Run("Succeeds if no element matched predicate", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{
+			"foo":  "bar",
+			"baz":  true,
+			"qux":  -1,
+			"quux": 2,
+		})
+		afterObject := object.NotFind(func(key string, value *Value) bool {
+			return key == "corge"
+		})
+		assert.Equal(t, map[string]interface{}{
+			"foo":  "bar",
+			"baz":  true,
+			"qux":  -1.0,
+			"quux": 2.0,
+		}, afterObject.Raw())
+		assert.Equal(t, object.Raw(), map[string]interface{}{
+			"foo":  "bar",
+			"baz":  true,
+			"qux":  -1.0,
+			"quux": 2.0,
+		})
+
+		afterObject.chain.assertNotFailed(t)
+		object.chain.assertNotFailed(t)
+	})
+
+	t.Run("Fails if there is a match", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{
+			"foo":  "bar",
+			"baz":  true,
+			"qux":  -1,
+			"quux": 2,
+		})
+		afterObject := object.NotFind(func(key string, value *Value) bool {
+			return key == "qux"
+		})
+		assert.Equal(t, map[string]interface{}(nil), afterObject.Raw())
+		assert.Equal(t, object.Raw(), map[string]interface{}{
+			"foo":  "bar",
+			"baz":  true,
+			"qux":  -1.0,
+			"quux": 2.0,
+		})
+
+		afterObject.chain.assertFailed(t)
+		object.chain.assertFailed(t)
+	})
+
+	t.Run("Empty object", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{})
+		afterObject := object.NotFind(func(key string, value *Value) bool {
+			return key == "corge"
+		})
+		assert.Equal(t, map[string]interface{}{}, afterObject.Raw())
+		assert.Equal(t, object.Raw(), map[string]interface{}{})
+
+		afterObject.chain.assertNotFailed(t)
+		object.chain.assertNotFailed(t)
+	})
+
+	t.Run("When predicate returns true, but assertion fails, predicate is failed",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			object := NewObject(reporter, map[string]interface{}{
+				"foo": 1,
+				"bar": 2,
+			})
+			afterObject := object.NotFind(func(key string, value *Value) bool {
+				value.String().Raw()
+				return true
+			})
+			assert.Equal(t, map[string]interface{}{
+				"foo": 1.0,
+				"bar": 2.0,
+			}, afterObject.Raw())
+			assert.Equal(t, object.Raw(), map[string]interface{}{
+				"foo": 1.0,
+				"bar": 2.0,
+			})
+
+			afterObject.chain.assertNotFailed(t)
+			object.chain.assertNotFailed(t)
+		})
+
+	t.Run("Predicate func is nil", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		object := NewObject(reporter, map[string]interface{}{
+			"foo": 1,
+			"bar": 2,
+		})
+		afterObject := object.NotFind(nil)
+		assert.Equal(t, map[string]interface{}(nil), afterObject.Raw())
+		assert.Equal(t, object.Raw(), map[string]interface{}{
+			"foo": 1.0,
+			"bar": 2.0,
+		})
+
+		afterObject.chain.assertFailed(t)
+		object.chain.assertFailed(t)
+	})
+}