Repository: gavv/httpexpect
Pull request #202 (2022-12-18 14:33:55)

Refactor formatValue
Issue: (part of) #190 

Refactoring before proceeding to introduce `DefaultFormatter.DisableScientific` as mentioned in https://github.com/gavv/httpexpect/issues/190#issuecomment-1356196337.

Refactoring (more detail https://github.com/gavv/httpexpect/issues/190#issuecomment-1356181798):

- Delete `formatFloat`. Replace with `formatValue`
- Rework `formatTyped` to use `formatValue`
- Rework `formatRange` to use `formatValue`
- Introduce _scientific_ as default float format

Tasks:

- [x] Add unit test to _catch_ potential breaking early
- [x] Refactor
- [x] Re-evaluate unit test

~PR will be draft, unless all tasks are checked~

PR is now ready

neverbeenthisweeb: hi @gavv , I've added unit test to guard from potential breaking when refactoring the `formatValue`

would you mind to take a look? thanks
coveralls: 
[![Coverage Status](https://coveralls.io/builds/55259075/badge)](https://coveralls.io/builds/55259075)

Coverage increased (+0.06%) to 94.625% when pulling **8e5e75e5be5a7a8c9680c0cfd9687e9dd60c6fcc on neverbeenthisweeb:190-enable-scientific-formatter** into **663cea3167b5b5a9cfd093713cb323142454b897 on gavv:master**.

gavv: Nice!

Tests look good, but I thought you were going to create tests based on FormatData instead of of format template?

My understanding of the discussion was that template-based tests will be e2e tests covered by #165.
neverbeenthisweeb: updated

- Remove underscore in test names
- Test buildFormatData instead of template
neverbeenthisweeb: @gavv hi, may i know what is the intention of `isNumber`?

i think it is a bit _vague_ when we see the existing `formatFloat`:

```go
func formatFloat(value interface{}) string {
	switch value.(type) {
	case float32, float64:
		return fmt.Sprintf("%f", value)
	}

	if isNumber(value) {
		return fmt.Sprintf("%v", value)
	}

	return formatValue(value)
}
```

`float32` and `float64` are also numbers right? why we differ them from `isNumber`'s check?
neverbeenthisweeb: @gavv and maybe one more about `formatValue`

is it safe for me to assume in `formatValue`:

https://github.com/gavv/httpexpect/blob/4e927161f9445c5bd17915a2c53f53da2d5825fb/formatter.go#L401

that this part

```
if s, _ := value.(fmt.Stringer); s != nil {
	if ss := s.String(); strings.TrimSpace(ss) != "" {
		return ss
	}
}
```

means the same  as `formatString`?

https://github.com/gavv/httpexpect/blob/4e927161f9445c5bd17915a2c53f53da2d5825fb/formatter.go#L420
gavv: > @gavv hi, may i know what is the intention of isNumber?

Intention was to use %f for floats and %v for other numbers (i.e. integers).
gavv: > is it safe for me to assume in formatValue:
> has the same intention as formatString?

IIRC they're different.

formatString is for printing bare strings. If value is `string`, it is printed as is, without adding quotes or anything. If it's not `string`, there is a fallback that uses formatValue. Other methods, e.g. formatRange, formatList, have similar fallbacks. Maybe it makes sense to rename them to formatStringOrValue, formatRangeOrValue, formatListOrValue.

formatValue is for printer arbitrary objects. It prints most values as JSON, with a few exceptions. If value is `http.*`, it prints  it using `litter`. If value implements `Stringer` interface, i.e. it's an object with String() method, it uses that method to format object. It allows to pass values with custom formatting representation, e.g. see wsMessageType and wsCloseCode.
gavv: Note that when you pass a `string` to formatValue, it will select json.Marshal branch and print the string with quotes (this is how JSON represents strings). And if you pass `string` to formatString, it will print it without quotes.

Maybe it makes sens to rename formatString to formatBareString (or formatBareStringOrValue) to make this detail clearer.
gavv: We could also rename formatTyped to formatValueWithType.
neverbeenthisweeb: @gavv ok thanks for the feedbacks

will revisit this pr either this weekend/next week

sry for late responses
gavv: Great, thanks! 

Patch:
diff --git a/.github/workflows/detect_conflicts.yml b/.github/workflows/detect_conflicts.yml
new file mode 100644
index 0000000..25e1198
--- /dev/null
+++ b/.github/workflows/detect_conflicts.yml
@@ -0,0 +1,20 @@
+name: "detect conflicts"
+
+on:
+  push:
+    branches:
+      - "*"
+    tags:
+      - "!*"
+  pull_request:
+    types: synchronize
+
+jobs:
+  main:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Run the action to detect unmergeable PRs
+        uses: cats-oss/github-action-detect-unmergeable@v2.1.1
+        env:
+          LABEL_NEED_REBASE: "needs rebase"
+          GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/remove_labels.yml b/.github/workflows/remove_labels.yml
new file mode 100644
index 0000000..905a37f
--- /dev/null
+++ b/.github/workflows/remove_labels.yml
@@ -0,0 +1,14 @@
+name: remove labels
+
+on:
+  pull_request:
+    types:
+      - closed
+
+jobs:
+  remove-labels:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: andymckay/labeler@master
+        with:
+          remove-labels: 'work in progress,ready for review,review in progress,needs revision'
diff --git a/README.md b/README.md
index b35b658..c5c6541 100644
--- a/README.md
+++ b/README.md
@@ -495,7 +495,7 @@ e := httpexpect.WithConfig(httpexpect.Config{
 
 	// use http.Client with a cookie jar and timeout
 	Client: &http.Client{
-		Jar:     httpexpect.NewJar(),
+		Jar:     httpexpect.NewCookieJar(),
 		Timeout: time.Second * 30,
 	},
 
@@ -522,7 +522,7 @@ e := httpexpect.WithConfig(httpexpect.Config{
 	Reporter: httpexpect.NewAssertReporter(t),
 	Client: &http.Client{
 		Transport: httpexpect.NewBinder(handler),
-		Jar:       httpexpect.NewJar(),
+		Jar:       httpexpect.NewCookieJar(),
 	},
 })
 
@@ -536,7 +536,7 @@ e := httpexpect.WithConfig(httpexpect.Config{
 	Reporter: httpexpect.NewAssertReporter(t),
 	Client: &http.Client{
 		Transport: httpexpect.NewFastBinder(handler),
-		Jar:       httpexpect.NewJar(),
+		Jar:       httpexpect.NewCookieJar(),
 	},
 })
 ```
@@ -592,7 +592,7 @@ e := httpexpect.WithConfig(httpexpect.Config{
 e := httpexpect.WithConfig(httpexpect.Config{
 	Reporter: httpexpect.NewAssertReporter(t),
 	Client: &http.Client{
-		Jar: httpexpect.NewJar(), // used by default if Client is nil
+		Jar: httpexpect.NewCookieJar(), // used by default if Client is nil
 	},
 })
 
diff --git a/array.go b/array.go
index f044935..2c20cfd 100644
--- a/array.go
+++ b/array.go
@@ -280,14 +280,14 @@ func (a *Array) Every(fn func(index int, value *Value)) *Array {
 }
 
 // Filter accepts a function that returns a boolean. The function is ran
-// over the array items. If the function returns true, the item passes
-// the filter and is added to the new array of filtered items. If false,
-// the item is skipped (or in other words filtered out). After iterating
-// through all the items of the original array, the new filtered array
+// over the array elements. If the function returns true, the element passes
+// the filter and is added to the new array of filtered elements. If false,
+// the element is skipped (or in other words filtered out). After iterating
+// through all the elements of the original array, the new filtered array
 // is returned.
 //
 // If there are any failed assertions in the filtering function, the
-// item is omitted without causing test failure.
+// element is omitted without causing test failure.
 //
 // Example:
 //
@@ -372,6 +372,176 @@ 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, runs it over the array
+// elements, and returns the first element on which it returned true.
+//
+// If there are any failed assertions in the predicate function, the
+// element is skipped without causing test failure.
+//
+// If no elements were found, a failure is reported.
+//
+// Example:
+//
+//	array := NewArray(t, []interface{}{1, "foo", 101, "bar", 201})
+//	foundValue := array.Find(func(index int, value *httpexpect.Value) bool {
+//		num := value.Number()    // skip if element is not a number
+//		return num.Raw() > 100   // check element value
+//	})
+//	foundValue.Equal(101) // 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 matches predicate"),
+		},
+	})
+
+	return newValue(a.chain, nil)
+}
+
+// FindAll accepts a function that returns a boolean, runs it over the array
+// elements, and returns all the elements on which it returned true.
+//
+// If there are any failed assertions in the predicate function, the
+// element is skipped without causing test failure.
+//
+// If no elements were found, empty slice is returned without reporting error.
+//
+// Example:
+//
+//	array := NewArray(t, []interface{}{1, "foo", 101, "bar", 201})
+//	foundValues := array.FindAll(func(index int, value *httpexpect.Value) bool {
+//		num := value.Number()   // skip if element is not a number
+//		return num.Raw() > 100  // check element value
+//	})
+//
+//	assert.Equal(t, len(foundValues), 2)
+//	foundValues[0].Equal(101)
+//	foundValues[1].Equal(201)
+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, runs it over the array
+// elelements, and checks that it does not return true for any of the elements.
+//
+// If there are any failed assertions in the predicate function, the
+// element is skipped without causing test failure.
+//
+// If the predicate function did not fail and returned true for at least
+// one element, a failure is reported.
+//
+// Example:
+//
+//	array := NewArray(t, []interface{}{1, "foo", 2, "bar"})
+//	array.NotFind(func(index int, value *httpexpect.Value) bool {
+//		num := value.Number()    // skip if element is not a number
+//		return num.Raw() > 100   // check element value
+//	}) // 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 a
+	}
+
+	if fn == nil {
+		a.chain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected nil function argument"),
+			},
+		})
+		return a
+	}
+
+	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 a
+		}
+	}
+
+	return a
+}
+
 // Empty succeeds if array is empty.
 //
 // Example:
diff --git a/assertion_validation.go b/assertion_validation.go
index c8c37b4..3edcb4f 100644
--- a/assertion_validation.go
+++ b/assertion_validation.go
@@ -171,7 +171,7 @@ func validateTraits(failure *AssertionFailure, traits fieldTraits) error {
 		break
 	}
 
-	if traits.Range == fieldRequired || traits.Range == fieldOptional {
+	if traits.List == fieldRequired || traits.List == fieldOptional {
 		if failure.Expected != nil {
 			if _, ok := failure.Expected.Value.(*AssertionList); ok {
 				return errors.New(
diff --git a/cookie_jar.go b/cookie_jar.go
index 6928e3a..7d2ce26 100644
--- a/cookie_jar.go
+++ b/cookie_jar.go
@@ -7,13 +7,13 @@ import (
 	"golang.org/x/net/publicsuffix"
 )
 
-// NewJar returns a new http.CookieJar.
+// NewCookieJar returns a new http.CookieJar.
 //
 // Returned jar is implemented in net/http/cookiejar. PublicSuffixList is
 // implemented in golang.org/x/net/publicsuffix.
 //
 // Note that this jar ignores cookies when request url is empty.
-func NewJar() http.CookieJar {
+func NewCookieJar() http.CookieJar {
 	jar, err := cookiejar.New(&cookiejar.Options{
 		PublicSuffixList: publicsuffix.List,
 	})
@@ -22,3 +22,8 @@ func NewJar() http.CookieJar {
 	}
 	return jar
 }
+
+// Deprecated: use NewCookieJar instead.
+func NewJar() http.CookieJar {
+	return NewCookieJar()
+}
diff --git a/expect.go b/expect.go
index ccc49cc..9382940 100644
--- a/expect.go
+++ b/expect.go
@@ -124,7 +124,7 @@ type Config struct {
 	//
 	// If nil, set to a default client with a non-nil Jar:
 	//  &http.Client{
-	//      Jar: httpexpect.NewJar(),
+	//      Jar: httpexpect.NewCookieJar(),
 	//  }
 	//
 	// You can use http.DefaultClient or your own http.Client, or provide
@@ -224,7 +224,7 @@ func (config Config) withDefaults() Config {
 
 	if config.Client == nil {
 		config.Client = &http.Client{
-			Jar: NewJar(),
+			Jar: NewCookieJar(),
 		}
 	}
 
@@ -393,7 +393,7 @@ func Default(t TestingTB, baseURL string) *Expect {
 //	        BaseURL:  "http://example.com/",
 //	        Client:   &http.Client{
 //	            Transport: httpexpect.NewBinder(myHandler()),
-//	            Jar:       httpexpect.NewJar(),
+//	            Jar:       httpexpect.NewCookieJar(),
 //	        },
 //	        Reporter: httpexpect.NewAssertReporter(t),
 //	        Printers: []httpexpect.Printer{
diff --git a/formatter.go b/formatter.go
index b21c15c..838905c 100644
--- a/formatter.go
+++ b/formatter.go
@@ -277,21 +277,21 @@ func (f *DefaultFormatter) fillExpected(
 		data.HaveExpected = true
 		data.ExpectedKind = kindSchema
 		data.Expected = []string{
-			formatString(failure.Expected.Value),
+			formatBareString(failure.Expected.Value),
 		}
 
 	case AssertMatchPath, AssertNotMatchPath:
 		data.HaveExpected = true
 		data.ExpectedKind = kindPath
 		data.Expected = []string{
-			formatString(failure.Expected.Value),
+			formatBareString(failure.Expected.Value),
 		}
 
 	case AssertMatchRegexp, AssertNotMatchRegexp:
 		data.HaveExpected = true
 		data.ExpectedKind = kindRegexp
 		data.Expected = []string{
-			formatString(failure.Expected.Value),
+			formatBareString(failure.Expected.Value),
 		}
 
 	case AssertMatchFormat, AssertNotMatchFormat:
@@ -391,14 +391,22 @@ func (f *DefaultFormatter) fillDelta(
 	data *FormatData, ctx *AssertionContext, failure *AssertionFailure,
 ) {
 	data.HaveDelta = true
-	data.Delta = formatFloat(failure.Delta.Value)
+	data.Delta = formatValue(failure.Delta.Value)
 }
 
 func formatTyped(value interface{}) string {
+	if isNumber(value) {
+		return fmt.Sprintf("%T(%v)", value, formatValue(value))
+	}
+
 	return fmt.Sprintf("%T(%#v)", value, value)
 }
 
 func formatValue(value interface{}) string {
+	if isNumber(value) {
+		return fmt.Sprintf("%v", value)
+	}
+
 	if !isNil(value) && !isHTTP(value) {
 		if s, _ := value.(fmt.Stringer); s != nil {
 			if ss := s.String(); strings.TrimSpace(ss) != "" {
@@ -417,7 +425,7 @@ func formatValue(value interface{}) string {
 	return sq.Sdump(value)
 }
 
-func formatString(value interface{}) string {
+func formatBareString(value interface{}) string {
 	switch v := value.(type) {
 	case string:
 		return v
@@ -426,24 +434,11 @@ func formatString(value interface{}) string {
 	return formatValue(value)
 }
 
-func formatFloat(value interface{}) string {
-	switch value.(type) {
-	case float32, float64:
-		return fmt.Sprintf("%f", value)
-	}
-
-	if isNumber(value) {
-		return fmt.Sprintf("%v", value)
-	}
-
-	return formatValue(value)
-}
-
 func formatRange(value interface{}) []string {
 	if rng := exctractRange(value); rng != nil {
 		if isNumber(rng.Min) && isNumber(rng.Max) {
 			return []string{
-				fmt.Sprintf("[%v; %v]", rng.Min, rng.Max),
+				fmt.Sprintf("[%v; %v]", formatValue(rng.Min), formatValue(rng.Max)),
 			}
 		} else {
 			return []string{
diff --git a/object.go b/object.go
index 863d39a..bc9132e 100644
--- a/object.go
+++ b/object.go
@@ -100,14 +100,10 @@ func (o *Object) Keys() *Array {
 	}
 
 	keys := []interface{}{}
-	for k := range o.value {
-		keys = append(keys, k)
+	for _, kv := range o.sortedKV() {
+		keys = append(keys, kv.key)
 	}
 
-	sort.Slice(keys, func(i, j int) bool {
-		return keys[i].(string) < keys[j].(string)
-	})
-
 	return newArray(o.chain, keys)
 }
 
@@ -126,18 +122,9 @@ func (o *Object) Values() *Array {
 		return newArray(o.chain, nil)
 	}
 
-	keys := []string{}
-	for k := range o.value {
-		keys = append(keys, k)
-	}
-
-	sort.Slice(keys, func(i, j int) bool {
-		return keys[i] < keys[j]
-	})
-
 	values := []interface{}{}
-	for _, k := range keys {
-		values = append(values, o.value[k])
+	for _, kv := range o.sortedKV() {
+		values = append(values, kv.val)
 	}
 
 	return newArray(o.chain, values)
@@ -258,14 +245,14 @@ func (o *Object) Every(fn func(key string, value *Value)) *Object {
 }
 
 // Filter accepts a function that returns a boolean. The function is ran
-// over the object items. If the function returns true, the item passes
-// the filter and is added to the new object of filtered items. If false,
+// over the object elements. If the function returns true, the element passes
+// the filter and is added to the new object of filtered elements. If false,
 // the value is skipped (or in other words filtered out). After iterating
-// through all the items of the original object, the new filtered object
+// through all the elements of the original object, the new filtered object
 // is returned.
 //
 // If there are any failed assertions in the filtering function, the
-// item is omitted without causing test failure.
+// element is omitted without causing test failure.
 //
 // Example:
 //
@@ -354,6 +341,200 @@ func (o *Object) Transform(fn func(key string, value interface{}) interface{}) *
 	return newObject(o.chain, object)
 }
 
+// Find accepts a function that returns a boolean, runs it over the object
+// elements, and returns the first element on which it returned true.
+//
+// If there are any failed assertions in the predicate function, the
+// element is skipped without causing test failure.
+//
+// If no elements were found, a failure is reported.
+//
+// The search is performed in the array of object elements sorted by keys.
+//
+// Example:
+//
+//	object := NewObject(t, map[string]interface{}{
+//		"a": 1,
+//		"b": "foo",
+//		"c": 101,
+//		"d": "bar",
+//		"e": 201,
+//	})
+//	foundValue := object.Find(func(key string, value *httpexpect.Value)  bool {
+//		num := value.Number()      // skip if element is not a string
+//		return num.Raw() > 100     // check element value
+//	})
+//	foundValue.Equal(101) // 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)
+	}
+
+	for _, kv := range o.sortedKV() {
+		valueChain := o.chain.clone()
+		valueChain.setSeverity(SeverityLog)
+		chainFailed := false
+		valueChain.setFailCallback(func() {
+			chainFailed = true
+		})
+		valueChain.replace("Find[%q]", kv.key)
+		if fn(kv.key, newValue(valueChain, kv.val)) && !chainFailed {
+			return newValue(o.chain, kv.val)
+		}
+	}
+
+	o.chain.fail(AssertionFailure{
+		Type:   AssertValid,
+		Actual: &AssertionValue{o.value},
+		Errors: []error{
+			errors.New("expected: at least one object element matches predicate"),
+		},
+	})
+
+	return newValue(o.chain, nil)
+}
+
+// FindAll accepts a function that returns a boolean, runs it over the object
+// elements, and returns all the elements on which it returned true.
+//
+// If there are any failed assertions in the predicate function, the
+// element is skipped without causing test failure.
+//
+// If no elements were found, empty slice is returned without reporting error.
+//
+// The search is performed in the array of object elements sorted by keys.
+//
+// Example:
+//
+//	object := NewObject(t, map[string]interface{}{
+//		"a": 1,
+//		"b": "foo",
+//		"c": 101,
+//		"d": "bar",
+//		"e": 201,
+//	})
+//	foundValues := object.FindAll(func(key string, value *httpexpect.Value)  bool {
+//		num := value.Number()      // skip if element is not a string
+//		return num.Raw() > 100     // check element value
+//	})
+//
+//	assert.Equal(t, len(foundValues), 2)
+//	foundValues[0].Equal(101)
+//	foundValues[1].Equal(201)
+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{}
+	}
+
+	foundValues := make([]Value, 0, len(o.value))
+
+	for _, kv := range o.sortedKV() {
+		valueChain := o.chain.clone()
+		valueChain.setSeverity(SeverityLog)
+		chainFailed := false
+		valueChain.setFailCallback(func() {
+			chainFailed = true
+		})
+		valueChain.replace("FindAll[%q]", kv.key)
+		if fn(kv.key, newValue(valueChain, kv.val)) && !chainFailed {
+			foundValues = append(foundValues, *newValue(o.chain, kv.val))
+		}
+	}
+
+	return foundValues
+}
+
+// NotFind accepts a function that returns a boolean, runs it over the object
+// elelements, and checks that it does not return true for any of the elements.
+//
+// If there are any failed assertions in the predicate function, the
+// element is skipped without causing test failure.
+//
+// If the predicate function did not fail and returned true for at least
+// one element, a failure is reported.
+//
+// The search is performed in the array of object elements sorted by keys.
+//
+// Example:
+//
+//	object := NewObject(t, map[string]interface{}{
+//		"a": 1,
+//		"b": "foo",
+//		"c": 2,
+//		"d": "bar",
+//	})
+//	object.NotFind(func(key string, value *httpexpect.Value) bool {
+//		num := value.Number()    // skip if element is not a number
+//		return num.Raw() > 100   // check element value
+//	}) // 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 o
+	}
+
+	if fn == nil {
+		o.chain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected nil function argument"),
+			},
+		})
+		return o
+	}
+
+	for _, kv := range o.sortedKV() {
+		valueChain := o.chain.clone()
+		valueChain.setSeverity(SeverityLog)
+		chainFailed := false
+		valueChain.setFailCallback(func() {
+			chainFailed = true
+		})
+		valueChain.replace("NotFind[%q]", kv.key)
+		if fn(kv.key, newValue(valueChain, kv.val)) && !chainFailed {
+			o.chain.fail(AssertionFailure{
+				Type:     AssertNotContainsElement,
+				Expected: &AssertionValue{kv.val},
+				Actual:   &AssertionValue{o.value},
+				Errors: []error{
+					errors.New("expected: none of the object elements match predicate"),
+				},
+			})
+			return o
+		}
+	}
+
+	return o
+}
+
 // Empty succeeds if object is empty.
 //
 // Example:
@@ -799,6 +980,20 @@ func (o *Object) ValueNotEqual(key string, value interface{}) *Object {
 	return o.NotValueEqual(key, value)
 }
 
+type kv struct {
+	key string
+	val interface{}
+}
+
+func (o *Object) sortedKV() []kv {
+	kvs := make([]kv, 0, len(o.value))
+	for key, val := range o.value {
+		kvs = append(kvs, kv{key: key, val: val})
+	}
+	sort.Slice(kvs, func(i, j int) bool { return kvs[i].key < kvs[j].key })
+	return kvs
+}
+
 func (o *Object) containsKey(arg string) bool {
 	for k := range o.value {
 		if k == arg {
diff --git a/request.go b/request.go
index 07ebfbb..f8a2784 100644
--- a/request.go
+++ b/request.go
@@ -338,7 +338,7 @@ func (r *Request) WithHandler(handler http.Handler) *Request {
 	} else {
 		r.config.Client = &http.Client{
 			Transport: NewBinder(handler),
-			Jar:       NewJar(),
+			Jar:       NewCookieJar(),
 		}
 	}
 


Test patch:
diff --git a/array_test.go b/array_test.go
index 902844e..6c90e46 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,322 @@ func TestArrayFilter(t *testing.T) {
 			filteredArray.chain.assertNotFailed(t)
 		})
 }
+
+func TestArrayFind(t *testing.T) {
+	t.Run("Elements 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("Elements of multiple 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("First 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 {
+			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("Predicate returns true, assertion fails, no match",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			array := NewArray(reporter, []interface{}{1, 2})
+			foundValue := array.Find(func(index int, value *Value) bool {
+				value.String()
+				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 returns true, assertion fails, have match",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			array := NewArray(reporter, []interface{}{1, 2, "str"})
+			foundValue := array.Find(func(index int, value *Value) bool {
+				value.String()
+				return true
+			})
+			assert.Equal(t, "str", foundValue.Raw())
+			assert.Equal(t, array.Raw(), []interface{}{1.0, 2.0, "str"})
+
+			array.chain.assertNotFailed(t)
+			foundValue.chain.assertNotFailed(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("Elements 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("Elements of multiple 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("Predicate returns true, assertion fails, no match",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			array := NewArray(reporter, []interface{}{1, 2})
+			foundValues := array.FindAll(func(index int, value *Value) bool {
+				value.String()
+				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("Predicate returns true, assertion fails, have 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()
+			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 matches 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.Same(t, array, afterArray)
+		array.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.Same(t, array, afterArray)
+		array.chain.assertFailed(t)
+	})
+
+	t.Run("Empty array", func(ts *testing.T) {
+		reporter := newMockReporter(t)
+		array := NewArray(reporter, []interface{}{})
+		afterArray := array.NotFind(func(index int, value *Value) bool {
+			return value.Raw() == 2.0
+		})
+		assert.Same(t, array, afterArray)
+		array.chain.assertNotFailed(t)
+	})
+
+	t.Run("Predicate returns true, assertion fails, no match",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			array := NewArray(reporter, []interface{}{1, 2})
+			afterArray := array.NotFind(func(index int, value *Value) bool {
+				value.String()
+				return true
+			})
+			assert.Same(t, array, afterArray)
+			array.chain.assertNotFailed(t)
+		})
+
+	t.Run("Predicate returns true, assertion fails, have match",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			array := NewArray(reporter, []interface{}{1, 2, "str"})
+			afterArray := array.NotFind(func(index int, value *Value) bool {
+				value.String()
+				return true
+			})
+			assert.Same(t, array, afterArray)
+			array.chain.assertFailed(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.Same(t, array, afterArray)
+		array.chain.assertFailed(t)
+	})
+}
diff --git a/chain_test.go b/chain_test.go
index 0c28c21..3f285a9 100644
--- a/chain_test.go
+++ b/chain_test.go
@@ -128,6 +128,12 @@ func TestChainPath(t *testing.T) {
 }
 
 func TestChainPanics(t *testing.T) {
+	t.Run("nil reporter", func(t *testing.T) {
+		assert.Panics(t, func() {
+			_ = newChainWithDefaults("test", nil)
+		})
+	})
+
 	t.Run("unpaired leave", func(t *testing.T) {
 		chain := newChainWithDefaults("", newMockReporter(t))
 
diff --git a/e2e_cookie_test.go b/e2e_cookie_test.go
index af34c46..f3521a9 100644
--- a/e2e_cookie_test.go
+++ b/e2e_cookie_test.go
@@ -78,7 +78,7 @@ func TestE2ECookieLiveEnabled(t *testing.T) {
 		BaseURL:  server.URL,
 		Reporter: NewAssertReporter(t),
 		Client: &http.Client{
-			Jar: NewJar(),
+			Jar: NewCookieJar(),
 		},
 	})
 
@@ -108,7 +108,7 @@ func TestE2ECookieBinderStandardEnabled(t *testing.T) {
 		Reporter: NewAssertReporter(t),
 		Client: &http.Client{
 			Transport: NewBinder(handler),
-			Jar:       NewJar(),
+			Jar:       NewCookieJar(),
 		},
 	})
 
@@ -138,7 +138,7 @@ func TestE2ECookieBinderFastEnabled(t *testing.T) {
 		Reporter: NewAssertReporter(t),
 		Client: &http.Client{
 			Transport: NewFastBinder(handler),
-			Jar:       NewJar(),
+			Jar:       NewCookieJar(),
 		},
 	})
 
diff --git a/e2e_fs_test.go b/e2e_fs_test.go
index 40733cc..6ba4d0c 100644
--- a/e2e_fs_test.go
+++ b/e2e_fs_test.go
@@ -31,7 +31,7 @@ func TestE2EFsFastBinder(t *testing.T) {
 	e := WithConfig(Config{
 		Client: &http.Client{
 			Transport: NewFastBinder(handler),
-			Jar:       NewJar(),
+			Jar:       NewCookieJar(),
 		},
 		Reporter: NewAssertReporter(t),
 		Printers: []Printer{
diff --git a/formatter_test.go b/formatter_test.go
index c55f0e8..c890766 100644
--- a/formatter_test.go
+++ b/formatter_test.go
@@ -52,8 +52,8 @@ func TestFormatValues(t *testing.T) {
 		checkAll(t, formatValue)
 	})
 
-	t.Run("formatString", func(t *testing.T) {
-		checkAll(t, formatString)
+	t.Run("formatBareString", func(t *testing.T) {
+		checkAll(t, formatBareString)
 	})
 
 	t.Run("formatRange", func(t *testing.T) {
@@ -93,3 +93,459 @@ func TestFormatDiff(t *testing.T) {
 	checkOK(map[string]interface{}{"a": 1}, map[string]interface{}{})
 	checkOK([]interface{}{"a"}, []interface{}{})
 }
+
+func TestFormatDataFailureActual(t *testing.T) {
+	tests := []struct {
+		name           string
+		assertionType  AssertionType
+		assertionValue interface{}
+		wantHaveActual bool
+		wantActual     string
+	}{
+		// AssertType
+		{
+			name:           "AssertType nil",
+			assertionType:  AssertType,
+			assertionValue: nil,
+			wantHaveActual: true,
+			wantActual:     "<nil>(<nil>)",
+		},
+		{
+			name:           "AssertType int",
+			assertionType:  AssertType,
+			assertionValue: int(1_000_000),
+			wantHaveActual: true,
+			wantActual:     "int(1000000)",
+		},
+		{
+			name:           "AssertType float32",
+			assertionType:  AssertType,
+			assertionValue: float32(1_000_000),
+			wantHaveActual: true,
+			wantActual:     "float32(1e+06)",
+		},
+		{
+			name:           "AssertType float64",
+			assertionType:  AssertType,
+			assertionValue: float64(1_000_000),
+			wantHaveActual: true,
+			wantActual:     "float64(1e+06)",
+		},
+		{
+			name:           "AssertType string",
+			assertionType:  AssertType,
+			assertionValue: "test string",
+			wantHaveActual: true,
+			wantActual:     "string(\"test string\")",
+		},
+		{
+			name:           "AssertType object",
+			assertionType:  AssertType,
+			assertionValue: struct{ Name string }{"test name"},
+			wantHaveActual: true,
+			wantActual:     "struct { Name string }(struct { Name string }{Name:\"test name\"})",
+		},
+
+		// AssertValid
+		{
+			name:           "AssertValid nil",
+			assertionType:  AssertValid,
+			assertionValue: nil,
+			wantHaveActual: true,
+			wantActual:     "nil",
+		},
+		{
+			name:           "AssertValid int",
+			assertionType:  AssertValid,
+			assertionValue: int(1_000_000),
+			wantHaveActual: true,
+			wantActual:     "1000000",
+		},
+		{
+			name:           "AssertValid float32",
+			assertionType:  AssertValid,
+			assertionValue: float32(1_000_000),
+			wantHaveActual: true,
+			wantActual:     "1e+06",
+		},
+		{
+			name:           "AssertValid float64",
+			assertionType:  AssertValid,
+			assertionValue: float64(1_000_000),
+			wantHaveActual: true,
+			wantActual:     "1e+06",
+		},
+		{
+			name:           "AssertValid string",
+			assertionType:  AssertValid,
+			assertionValue: "test string",
+			wantHaveActual: true,
+			wantActual:     "\"test string\"",
+		},
+		{
+			name:           "AssertValid object",
+			assertionType:  AssertValid,
+			assertionValue: struct{ Name string }{"test name"},
+			wantHaveActual: true,
+			wantActual:     "{\n  \"Name\": \"test name\"\n}",
+		},
+	}
+
+	df := &DefaultFormatter{}
+	ctx := &AssertionContext{}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			fl := &AssertionFailure{
+				Type: tc.assertionType,
+				Actual: &AssertionValue{
+					Value: tc.assertionValue,
+				},
+			}
+			fd := df.buildFormatData(ctx, fl)
+			assert.Equal(t, tc.wantHaveActual, fd.HaveActual)
+			assert.Equal(t, tc.wantActual, fd.Actual)
+		})
+	}
+}
+
+func TestFormatDataFailureExpected(t *testing.T) {
+	tests := []struct {
+		name             string
+		assertionType    AssertionType
+		assertionValue   interface{}
+		wantHaveExpected bool
+		wantExpectedKind string
+		wantExpected     []string
+	}{
+		// AssertInRange
+		{
+			name:          "AssertInRange nil",
+			assertionType: AssertInRange,
+			assertionValue: AssertionRange{
+				Min: nil,
+				Max: nil,
+			},
+			wantHaveExpected: true,
+			wantExpectedKind: kindRange,
+			wantExpected:     []string{"<nil>", "<nil>"},
+		},
+		{
+			name:          "AssertInRange int",
+			assertionType: AssertInRange,
+			assertionValue: AssertionRange{
+				Min: int(1_000_000),
+				Max: int(2_000_000),
+			},
+			wantHaveExpected: true,
+			wantExpectedKind: kindRange,
+			wantExpected:     []string{"[1000000; 2000000]"},
+		},
+		{
+			name:          "AssertInRange float32",
+			assertionType: AssertInRange,
+			assertionValue: AssertionRange{
+				Min: float32(1_000_000),
+				Max: float32(2_000_000),
+			},
+			wantHaveExpected: true,
+			wantExpectedKind: kindRange,
+			wantExpected:     []string{"[1e+06; 2e+06]"},
+		},
+		{
+			name:          "AssertInRange float64",
+			assertionType: AssertInRange,
+			assertionValue: AssertionRange{
+				Min: float64(1_000_000),
+				Max: float64(2_000_000),
+			},
+			wantHaveExpected: true,
+			wantExpectedKind: kindRange,
+			wantExpected:     []string{"[1e+06; 2e+06]"},
+		},
+		{
+			name:          "AssertInRange string",
+			assertionType: AssertInRange,
+			assertionValue: AssertionRange{
+				Min: "test string 1",
+				Max: "test string 2",
+			},
+			wantHaveExpected: true,
+			wantExpectedKind: kindRange,
+			wantExpected:     []string{"test string 1", "test string 2"},
+		},
+		{
+			name:          "AssertInRange object",
+			assertionType: AssertInRange,
+			assertionValue: AssertionRange{
+				Min: struct{ Name string }{"test name 1"},
+				Max: struct{ Name string }{"test name 2"},
+			},
+			wantHaveExpected: true,
+			wantExpectedKind: kindRange,
+			wantExpected:     []string{"{test name 1}", "{test name 2}"},
+		},
+
+		// AssertMatchPath
+		{
+			name:             "AssertMatchPath nil",
+			assertionType:    AssertMatchPath,
+			assertionValue:   nil,
+			wantHaveExpected: true,
+			wantExpectedKind: kindPath,
+			wantExpected:     []string{"nil"},
+		},
+		{
+			name:             "AssertMatchPath int",
+			assertionType:    AssertMatchPath,
+			assertionValue:   int(1_000_000),
+			wantHaveExpected: true,
+			wantExpectedKind: kindPath,
+			wantExpected:     []string{"1000000"},
+		},
+		{
+			name:             "AssertMatchPath float32",
+			assertionType:    AssertMatchPath,
+			assertionValue:   float32(1_000_000),
+			wantHaveExpected: true,
+			wantExpectedKind: kindPath,
+			wantExpected:     []string{"1e+06"},
+		},
+		{
+			name:             "AssertMatchPath float64",
+			assertionType:    AssertMatchPath,
+			assertionValue:   float64(1_000_000),
+			wantHaveExpected: true,
+			wantExpectedKind: kindPath,
+			wantExpected:     []string{"1e+06"},
+		},
+		{
+			name:             "AssertMatchPath string",
+			assertionType:    AssertMatchPath,
+			assertionValue:   "test string",
+			wantHaveExpected: true,
+			wantExpectedKind: kindPath,
+			wantExpected:     []string{"test string"},
+		},
+		{
+			name:             "AssertMatchPath object",
+			assertionType:    AssertMatchPath,
+			assertionValue:   struct{ Name string }{"test name"},
+			wantHaveExpected: true,
+			wantExpectedKind: kindPath,
+			wantExpected:     []string{"{\n  \"Name\": \"test name\"\n}"},
+		},
+
+		// AssertMatchFormat
+		{
+			name:          "AssertMatchFormat nil",
+			assertionType: AssertMatchFormat,
+			assertionValue: AssertionList{
+				nil,
+				nil,
+			},
+			wantHaveExpected: true,
+			wantExpectedKind: kindFormatList,
+			wantExpected:     []string{"nil", "nil"},
+		},
+		{
+			name:          "AssertMatchFormat int",
+			assertionType: AssertMatchFormat,
+			assertionValue: AssertionList{
+				int(1_000_000),
+				int(2_000_000),
+			},
+			wantHaveExpected: true,
+			wantExpectedKind: kindFormatList,
+			wantExpected:     []string{"1000000", "2000000"},
+		},
+		{
+			name:          "AssertMatchFormat float32",
+			assertionType: AssertMatchFormat,
+			assertionValue: AssertionList{
+				float32(1_000_000),
+				float32(2_000_000),
+			},
+			wantHaveExpected: true,
+			wantExpectedKind: kindFormatList,
+			wantExpected:     []string{"1e+06", "2e+06"},
+		},
+		{
+			name:          "AssertMatchFormat float64",
+			assertionType: AssertMatchFormat,
+			assertionValue: AssertionList{
+				float64(1_000_000),
+				float64(2_000_000),
+			},
+			wantHaveExpected: true,
+			wantExpectedKind: kindFormatList,
+			wantExpected:     []string{"1e+06", "2e+06"},
+		},
+		{
+			name:          "AssertMatchFormat string",
+			assertionType: AssertMatchFormat,
+			assertionValue: AssertionList{
+				"test string 1",
+				"test string 2",
+			},
+			wantHaveExpected: true,
+			wantExpectedKind: kindFormatList,
+			wantExpected:     []string{"\"test string 1\"", "\"test string 2\""},
+		},
+		{
+			name:          "AssertMatchFormat object",
+			assertionType: AssertMatchFormat,
+			assertionValue: AssertionList{
+				struct{ Name string }{"test name 1"},
+				struct{ Name string }{"test name 2"},
+			},
+			wantHaveExpected: true,
+			wantExpectedKind: kindFormatList,
+			wantExpected: []string{
+				"{\n  \"Name\": \"test name 1\"\n}", "{\n  \"Name\": \"test name 2\"\n}",
+			},
+		},
+	}
+
+	df := &DefaultFormatter{}
+	ctx := &AssertionContext{}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			fl := &AssertionFailure{
+				Type: tc.assertionType,
+				Expected: &AssertionValue{
+					Value: tc.assertionValue,
+				},
+			}
+			fd := df.buildFormatData(ctx, fl)
+			assert.Equal(t, tc.wantHaveExpected, fd.HaveExpected)
+			assert.Equal(t, tc.wantExpectedKind, fd.ExpectedKind)
+			assert.Equal(t, tc.wantExpected, fd.Expected)
+		})
+	}
+}
+
+func TestFormatDataFailureReference(t *testing.T) {
+	tests := []struct {
+		name              string
+		assertionValue    interface{}
+		wantHaveReference bool
+		wantReference     string
+	}{
+		{
+			name:              "nil",
+			assertionValue:    nil,
+			wantHaveReference: true,
+			wantReference:     "nil",
+		},
+		{
+			name:              "int",
+			assertionValue:    int(1_000_000),
+			wantHaveReference: true,
+			wantReference:     "1000000",
+		},
+		{
+			name:              "float32",
+			assertionValue:    float32(1_000_000),
+			wantHaveReference: true,
+			wantReference:     "1e+06",
+		},
+		{
+			name:              "float64",
+			assertionValue:    float64(1_000_000),
+			wantHaveReference: true,
+			wantReference:     "1e+06",
+		},
+		{
+			name:              "string",
+			assertionValue:    "test string",
+			wantHaveReference: true,
+			wantReference:     "\"test string\"",
+		},
+		{
+			name:              "object",
+			assertionValue:    struct{ Name string }{"test name"},
+			wantHaveReference: true,
+			wantReference:     "{\n  \"Name\": \"test name\"\n}",
+		},
+	}
+
+	df := &DefaultFormatter{}
+	ctx := &AssertionContext{}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			fl := &AssertionFailure{
+				Reference: &AssertionValue{
+					Value: tc.assertionValue,
+				},
+			}
+			fd := df.buildFormatData(ctx, fl)
+			assert.Equal(t, tc.wantHaveReference, fd.HaveReference)
+			assert.Equal(t, tc.wantReference, fd.Reference)
+		})
+	}
+}
+
+func TestFormatDataFailureDelta(t *testing.T) {
+	tests := []struct {
+		name           string
+		assertionValue interface{}
+		wantHaveDelta  bool
+		wantDelta      string
+	}{
+		{
+			name:           "nil",
+			assertionValue: nil,
+			wantHaveDelta:  true,
+			wantDelta:      "nil",
+		},
+		{
+			name:           "int",
+			assertionValue: int(1_000_000),
+			wantHaveDelta:  true,
+			wantDelta:      "1000000",
+		},
+		{
+			name:           "float32",
+			assertionValue: float32(1_000_000),
+			wantHaveDelta:  true,
+			wantDelta:      "1e+06",
+		},
+		{
+			name:           "float64",
+			assertionValue: float64(1_000_000),
+			wantHaveDelta:  true,
+			wantDelta:      "1e+06",
+		},
+		{
+			name:           "string",
+			assertionValue: "test string",
+			wantHaveDelta:  true,
+			wantDelta:      "\"test string\"",
+		},
+		{
+			name:           "object",
+			assertionValue: struct{ Name string }{"test name"},
+			wantHaveDelta:  true,
+			wantDelta:      "{\n  \"Name\": \"test name\"\n}",
+		},
+	}
+
+	df := &DefaultFormatter{}
+	ctx := &AssertionContext{}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			fl := &AssertionFailure{
+				Delta: &AssertionValue{
+					Value: tc.assertionValue,
+				},
+			}
+			fd := df.buildFormatData(ctx, fl)
+			assert.Equal(t, tc.wantHaveDelta, fd.HaveDelta)
+			assert.Equal(t, tc.wantDelta, fd.Delta)
+		})
+	}
+}
diff --git a/object_test.go b/object_test.go
index b407263..339cf3a 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,434 @@ func TestObjectFilter(t *testing.T) {
 			object.chain.assertNotFailed(t)
 		})
 }
+
+func TestObjectFind(t *testing.T) {
+	t.Run("Elements of the same type", 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("Elements of multiple types", 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("Predicate returns true, assertion fails, no match",
+		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()
+				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 returns true, assertion fails, have match",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			object := NewObject(reporter, map[string]interface{}{
+				"foo": 1,
+				"bar": 2,
+				"baz": "str",
+			})
+			foundValue := object.Find(func(key string, value *Value) bool {
+				value.String()
+				return true
+			})
+			assert.Equal(t, "str", foundValue.Raw())
+			assert.Equal(t, object.Raw(), map[string]interface{}{
+				"foo": 1.0,
+				"bar": 2.0,
+				"baz": "str",
+			})
+
+			foundValue.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,
+		})
+		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("Elements 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("Elements of multiple 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 object", 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("Predicate returns true, assertion fails, no match",
+		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()
+				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("Predicate returns true, assertion fails, have match", 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()
+			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 matches 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.Same(t, object, afterObject)
+		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.Same(t, object, afterObject)
+		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.Same(t, object, afterObject)
+		object.chain.assertNotFailed(t)
+	})
+
+	t.Run("Predicate returns true, assertion fails, no match",
+		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()
+				return true
+			})
+			assert.Same(t, object, afterObject)
+			object.chain.assertNotFailed(t)
+		})
+
+	t.Run("Predicate returns true, assertion fails, have match",
+		func(ts *testing.T) {
+			reporter := newMockReporter(t)
+			object := NewObject(reporter, map[string]interface{}{
+				"foo": 1,
+				"bar": 2,
+				"baz": "str",
+			})
+			afterObject := object.NotFind(func(key string, value *Value) bool {
+				value.String()
+				return true
+			})
+			assert.Same(t, object, afterObject)
+			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,
+		})
+		afterObject := object.NotFind(nil)
+		assert.Same(t, object, afterObject)
+		object.chain.assertFailed(t)
+	})
+}
diff --git a/request_test.go b/request_test.go
index 658a48e..cfa4f69 100644
--- a/request_test.go
+++ b/request_test.go
@@ -316,7 +316,7 @@ func TestRequestHandlerResueClient(t *testing.T) {
 
 	client := &http.Client{
 		Transport: NewBinder(handler1),
-		Jar:       NewJar(),
+		Jar:       NewCookieJar(),
 	}
 
 	reporter := newMockReporter(t)