Repository: gavv/httpexpect
Pull request #302 (2023-02-21 21:57:34)

Refactor value unit tests
This PR addresses refactoring the following unit tests from #284:
- [x] TestValue_GetObject
- [x] TestValue_GetArray
- [x] TestValue_GetNumber
- [x] TestValue_GetBoolean
- [x] TestValue_IsObject
- [x] TestValue_IsArray
- [x] TestValue_IsString
- [x] TestValue_IsNumber
- [x] TestValue_IsBoolean

Most tests were converted to be table-driven. This is ready for review.


Patch:
diff --git a/number.go b/number.go
index 32150bb..389a858 100644
--- a/number.go
+++ b/number.go
@@ -2,7 +2,9 @@ package httpexpect
 
 import (
 	"errors"
+	"fmt"
 	"math"
+	"math/big"
 )
 
 // Number provides methods to inspect attached float64 value
@@ -618,3 +620,474 @@ func (n *Number) Le(value interface{}) *Number {
 
 	return n
 }
+
+// IsInt succeeds if number is a signed integer of the specified bit width
+// as an optional argument.
+//
+// Bits argument defines maximum allowed bitness for the given number.
+// If bits is omitted, boundary check is omitted too.
+//
+// Example:
+//
+//	number := NewNumber(t, 1000000)
+//	number.IsInt()   // success
+//	number.IsInt(32) // success
+//	number.IsInt(16) // failure
+//
+//	number := NewNumber(t, -1000000)
+//	number.IsInt()   // success
+//	number.IsInt(32) // success
+//	number.IsInt(16) // failure
+//
+//	number := NewNumber(t, 0.5)
+//	number.IsInt()   // failure
+func (n *Number) IsInt(bits ...int) *Number {
+	opChain := n.chain.enter("IsInt()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return n
+	}
+
+	if len(bits) > 1 {
+		opChain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected multiple bits arguments"),
+			},
+		})
+		return n
+	}
+
+	if len(bits) == 1 && bits[0] <= 0 {
+		opChain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected non-positive bits argument"),
+			},
+		})
+		return n
+	}
+
+	if math.IsNaN(n.value) {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{n.value},
+			Errors: []error{
+				errors.New("expected: number is signed integer"),
+			},
+		})
+		return n
+	}
+
+	inum, acc := big.NewFloat(n.value).Int(nil)
+	if !(acc == big.Exact) {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{n.value},
+			Errors: []error{
+				errors.New("expected: number is signed integer"),
+			},
+		})
+		return n
+	}
+
+	if len(bits) > 0 {
+		bitSize := bits[0]
+
+		imax := new(big.Int)
+		imax.Lsh(big.NewInt(1), uint(bitSize-1))
+		imax.Sub(imax, big.NewInt(1))
+		imin := new(big.Int)
+		imin.Neg(imax)
+		imin.Sub(imin, big.NewInt(1))
+		if inum.Cmp(imin) < 0 || inum.Cmp(imax) > 0 {
+			opChain.fail(AssertionFailure{
+				Type:   AssertInRange,
+				Actual: &AssertionValue{n.value},
+				Expected: &AssertionValue{AssertionRange{
+					Min: intBoundary{imin, -1, bitSize - 1},
+					Max: intBoundary{imax, +1, bitSize - 1},
+				}},
+				Errors: []error{
+					fmt.Errorf("expected: number is %d-bit signed integer", bitSize),
+				},
+			})
+			return n
+		}
+	}
+
+	return n
+}
+
+// NotInt succeeds if number is not a signed integer of the specified bit
+// width as an optional argument.
+//
+// Bits argument defines maximum allowed bitness for the given number.
+// If bits is omitted, boundary check is omitted too.
+//
+// Example:
+//
+//	number := NewNumber(t, 1000000)
+//	number.NotInt()   // failure
+//	number.NotInt(32) // failure
+//	number.NotInt(16) // success
+//
+//	number := NewNumber(t, -1000000)
+//	number.NotInt()   // failure
+//	number.NotInt(32) // failure
+//	number.NotInt(16) // success
+//
+//	number := NewNumber(t, 0.5)
+//	number.NotInt()   // success
+func (n *Number) NotInt(bits ...int) *Number {
+	opChain := n.chain.enter("NotInt()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return n
+	}
+
+	if len(bits) > 1 {
+		opChain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected multiple bits arguments"),
+			},
+		})
+		return n
+	}
+
+	if len(bits) == 1 && bits[0] <= 0 {
+		opChain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected non-positive bits argument"),
+			},
+		})
+		return n
+	}
+
+	if !math.IsNaN(n.value) {
+		inum, acc := big.NewFloat(n.value).Int(nil)
+		if acc == big.Exact {
+			if len(bits) == 0 {
+				opChain.fail(AssertionFailure{
+					Type:   AssertValid,
+					Actual: &AssertionValue{n.value},
+					Errors: []error{
+						errors.New("expected: number is not signed integer"),
+					},
+				})
+				return n
+			}
+
+			bitSize := bits[0]
+			imax := new(big.Int)
+			imax.Lsh(big.NewInt(1), uint(bitSize-1))
+			imax.Sub(imax, big.NewInt(1))
+			imin := new(big.Int)
+			imin.Neg(imax)
+			imin.Sub(imin, big.NewInt(1))
+			if !(inum.Cmp(imin) < 0 || inum.Cmp(imax) > 0) {
+				opChain.fail(AssertionFailure{
+					Type:   AssertNotInRange,
+					Actual: &AssertionValue{n.value},
+					Expected: &AssertionValue{AssertionRange{
+						Min: intBoundary{imin, -1, bitSize - 1},
+						Max: intBoundary{imax, +1, bitSize - 1},
+					}},
+					Errors: []error{
+						fmt.Errorf(
+							"expected: number doesn't fit %d-bit signed integer",
+							bitSize),
+					},
+				})
+				return n
+			}
+		}
+	}
+
+	return n
+}
+
+// IsUint succeeds if number is an unsigned integer of the specified bit
+// width as an optional argument.
+//
+// Bits argument defines maximum allowed bitness for the given number.
+// If bits is omitted, boundary check is omitted too.
+//
+// Example:
+//
+//	number := NewNumber(t, 1000000)
+//	number.IsUint()   // success
+//	number.IsUint(32) // success
+//	number.IsUint(16) // failure
+//
+//	number := NewNumber(t, -1000000)
+//	number.IsUint()   // failure
+//	number.IsUint(32) // failure
+//	number.IsUint(16) // failure
+//
+//	number := NewNumber(t, 0.5)
+//	number.IsUint()   // failure
+func (n *Number) IsUint(bits ...int) *Number {
+	opChain := n.chain.enter("IsUint()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return n
+	}
+
+	if len(bits) > 1 {
+		opChain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected multiple bits arguments"),
+			},
+		})
+		return n
+	}
+
+	if len(bits) == 1 && bits[0] <= 0 {
+		opChain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected non-positive bits argument"),
+			},
+		})
+		return n
+	}
+
+	if math.IsNaN(n.value) {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{n.value},
+			Errors: []error{
+				errors.New("expected: number is unsigned integer"),
+			},
+		})
+		return n
+	}
+
+	inum, acc := big.NewFloat(n.value).Int(nil)
+	if !(acc == big.Exact) {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{n.value},
+			Errors: []error{
+				errors.New("expected: number is unsigned integer"),
+			},
+		})
+		return n
+	}
+
+	imin := big.NewInt(0)
+	if inum.Cmp(imin) < 0 {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{n.value},
+			Errors: []error{
+				errors.New("expected: number is unsigned integer"),
+			},
+		})
+		return n
+	}
+
+	if len(bits) > 0 {
+		bitSize := bits[0]
+		imax := new(big.Int)
+		imax.Lsh(big.NewInt(1), uint(bitSize))
+		imax.Sub(imax, big.NewInt(1))
+		if inum.Cmp(imax) > 0 {
+			opChain.fail(AssertionFailure{
+				Type:   AssertInRange,
+				Actual: &AssertionValue{n.value},
+				Expected: &AssertionValue{AssertionRange{
+					Min: intBoundary{imin, 0, 0},
+					Max: intBoundary{imax, +1, bitSize},
+				}},
+				Errors: []error{
+					fmt.Errorf("expected: number fits %d-bit unsigned integer", bitSize),
+				},
+			})
+			return n
+		}
+	}
+
+	return n
+}
+
+// NotUint succeeds if number is not an unsigned integer of the specified bit
+// width as an optional argument.
+//
+// Bits argument defines maximum allowed bitness for the given number.
+// If bits is omitted, boundary check is omitted too.
+//
+// Example:
+//
+//	number := NewNumber(t, 1000000)
+//	number.NotUint()   // failure
+//	number.NotUint(32) // failure
+//	number.NotUint(16) // success
+//
+//	number := NewNumber(t, -1000000)
+//	number.NotUint()   // success
+//	number.NotUint(32) // success
+//	number.NotUint(16) // success
+//
+//	number := NewNumber(t, 0.5)
+//	number.NotUint()   // success
+func (n *Number) NotUint(bits ...int) *Number {
+	opChain := n.chain.enter("NotUint()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return n
+	}
+
+	if len(bits) > 1 {
+		opChain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected multiple bits arguments"),
+			},
+		})
+		return n
+	}
+
+	if len(bits) == 1 && bits[0] <= 0 {
+		opChain.fail(AssertionFailure{
+			Type: AssertUsage,
+			Errors: []error{
+				errors.New("unexpected non-positive bits argument"),
+			},
+		})
+		return n
+	}
+
+	if !math.IsNaN(n.value) {
+		inum, acc := big.NewFloat(n.value).Int(nil)
+		if acc == big.Exact {
+			imin := big.NewInt(0)
+			if inum.Cmp(imin) >= 0 {
+				if len(bits) == 0 {
+					opChain.fail(AssertionFailure{
+						Type:   AssertValid,
+						Actual: &AssertionValue{n.value},
+						Errors: []error{
+							errors.New("expected: number is not unsigned integer"),
+						},
+					})
+					return n
+				}
+
+				bitSize := bits[0]
+				imax := new(big.Int)
+				imax.Lsh(big.NewInt(1), uint(bitSize))
+				imax.Sub(imax, big.NewInt(1))
+				if inum.Cmp(imax) <= 0 {
+					opChain.fail(AssertionFailure{
+						Type:   AssertNotInRange,
+						Actual: &AssertionValue{n.value},
+						Expected: &AssertionValue{AssertionRange{
+							Min: intBoundary{imin, 0, 0},
+							Max: intBoundary{imax, +1, bitSize},
+						}},
+						Errors: []error{
+							fmt.Errorf(
+								"expected: number doesn't fit %d-bit unsigned integer",
+								bitSize),
+						},
+					})
+					return n
+				}
+			}
+		}
+	}
+
+	return n
+}
+
+// IsFinite succeeds if number is neither ±Inf nor NaN.
+//
+// Example:
+//
+//	number := NewNumber(t, 1234.5)
+//	number.IsFinite() // success
+//
+//	number := NewNumber(t, math.NaN())
+//	number.IsFinite() // failure
+//
+//	number := NewNumber(t, math.Inf(+1))
+//	number.IsFinite() // failure
+func (n *Number) IsFinite() *Number {
+	opChain := n.chain.enter("IsFinite()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return n
+	}
+
+	if math.IsInf(n.value, 0) || math.IsNaN(n.value) {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{n.value},
+			Errors: []error{
+				errors.New("expected: number is neither ±Inf nor NaN"),
+			},
+		})
+		return n
+	}
+
+	return n
+}
+
+// NotFinite succeeds if number is either ±Inf or NaN.
+//
+// Example:
+//
+//	number := NewNumber(t, 1234.5)
+//	number.NotFinite() // failure
+//
+//	number := NewNumber(t, math.NaN())
+//	number.NotFinite() // success
+//
+//	number := NewNumber(t, math.Inf(+1))
+//	number.NotFinite() // success
+func (n *Number) NotFinite() *Number {
+	opChain := n.chain.enter("NotFinite()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return n
+	}
+
+	if !(math.IsInf(n.value, 0) || math.IsNaN(n.value)) {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{n.value},
+			Errors: []error{
+				errors.New("expected: number is either ±Inf or NaN"),
+			},
+		})
+		return n
+	}
+
+	return n
+}
+
+type intBoundary struct {
+	val  *big.Int
+	sign int
+	bits int
+}
+
+func (b intBoundary) String() string {
+	if b.sign > 0 {
+		return fmt.Sprintf("+2^%d-1 (+%s)", b.bits, b.val)
+	} else if b.sign < 0 {
+		return fmt.Sprintf("-2^%d   (%s)", b.bits, b.val)
+	}
+	return fmt.Sprintf("%s", b.val)
+}


Test patch:
diff --git a/number_test.go b/number_test.go
index b47164d..95161df 100644
--- a/number_test.go
+++ b/number_test.go
@@ -33,6 +33,12 @@ func TestNumber_FailedChain(t *testing.T) {
 	value.Ge(0)
 	value.Lt(0)
 	value.Le(0)
+	value.IsInt()
+	value.NotInt()
+	value.IsUint()
+	value.NotUint()
+	value.IsFinite()
+	value.NotFinite()
 }
 
 func TestNumber_Constructors(t *testing.T) {
@@ -584,3 +590,366 @@ func TestNumber_ConvertLesser(t *testing.T) {
 	value.chain.assertFailed(t)
 	value.chain.clearFailed()
 }
+
+func TestNumber_IsInt(t *testing.T) {
+	t.Run("invalid argument", func(t *testing.T) {
+		reporter := newMockReporter(t)
+
+		NewNumber(reporter, 1234).IsInt(16, 32).
+			chain.assertFailed(t)
+
+		NewNumber(reporter, 1234).NotInt(16, 32).
+			chain.assertFailed(t)
+
+		NewNumber(reporter, 1234).IsInt(0).
+			chain.assertFailed(t)
+
+		NewNumber(reporter, 1234).NotInt(0).
+			chain.assertFailed(t)
+
+		NewNumber(reporter, 1234).IsInt(-16).
+			chain.assertFailed(t)
+
+		NewNumber(reporter, 1234).NotInt(-16).
+			chain.assertFailed(t)
+	})
+
+	t.Run("values", func(t *testing.T) {
+		tests := []struct {
+			name    string
+			value   float64
+			isInt16 bool
+			isInt32 bool
+			isInt   bool
+		}{
+			{
+				name:    "0",
+				value:   0,
+				isInt16: true,
+				isInt32: true,
+				isInt:   true,
+			},
+			{
+				name:    "1",
+				value:   1,
+				isInt16: true,
+				isInt32: true,
+				isInt:   true,
+			},
+			{
+				name:    "0.5",
+				value:   0.5,
+				isInt16: false,
+				isInt32: false,
+				isInt:   false,
+			},
+			{
+				name:    "NaN",
+				value:   math.NaN(),
+				isInt16: false,
+				isInt32: false,
+				isInt:   false,
+			},
+			{
+				name:    "-Inf",
+				value:   math.Inf(-1),
+				isInt16: false,
+				isInt32: false,
+				isInt:   false,
+			},
+			{
+				name:    "+Inf",
+				value:   math.Inf(+1),
+				isInt16: false,
+				isInt32: false,
+				isInt:   false,
+			},
+			{
+				name:    "MinInt16-1",
+				value:   math.MinInt16 - 1,
+				isInt16: false,
+				isInt32: true,
+				isInt:   true,
+			},
+			{
+				name:    "MinInt16",
+				value:   math.MinInt16,
+				isInt16: true,
+				isInt32: true,
+				isInt:   true,
+			},
+			{
+				name:    "MaxInt16",
+				value:   math.MaxInt16,
+				isInt16: true,
+				isInt32: true,
+				isInt:   true,
+			},
+			{
+				name:    "MaxInt16+1",
+				value:   math.MaxInt16 + 1,
+				isInt16: false,
+				isInt32: true,
+				isInt:   true,
+			},
+			{
+				name:    "MinInt32-1",
+				value:   math.MinInt32 - 1,
+				isInt16: false,
+				isInt32: false,
+				isInt:   true,
+			},
+			{
+				name:    "MinInt32",
+				value:   math.MinInt32,
+				isInt16: false,
+				isInt32: true,
+				isInt:   true,
+			},
+			{
+				name:    "MaxInt32",
+				value:   math.MaxInt32,
+				isInt16: false,
+				isInt32: true,
+				isInt:   true,
+			},
+			{
+				name:    "MaxInt32+1",
+				value:   math.MaxInt32 + 1,
+				isInt16: false,
+				isInt32: false,
+				isInt:   true,
+			},
+		}
+
+		for _, tc := range tests {
+			t.Run(tc.name, func(t *testing.T) {
+				reporter := newMockReporter(t)
+
+				if tc.isInt {
+					NewNumber(reporter, tc.value).IsInt().chain.assertNotFailed(t)
+					NewNumber(reporter, tc.value).NotInt().chain.assertFailed(t)
+				} else {
+					NewNumber(reporter, tc.value).IsInt().chain.assertFailed(t)
+					NewNumber(reporter, tc.value).NotInt().chain.assertNotFailed(t)
+				}
+
+				if tc.isInt32 {
+					NewNumber(reporter, tc.value).IsInt(32).chain.assertNotFailed(t)
+					NewNumber(reporter, tc.value).NotInt(32).chain.assertFailed(t)
+				} else {
+					NewNumber(reporter, tc.value).IsInt(32).chain.assertFailed(t)
+					NewNumber(reporter, tc.value).NotInt(32).chain.assertNotFailed(t)
+				}
+
+				if tc.isInt16 {
+					NewNumber(reporter, tc.value).IsInt(16).chain.assertNotFailed(t)
+					NewNumber(reporter, tc.value).NotInt(16).chain.assertFailed(t)
+				} else {
+					NewNumber(reporter, tc.value).IsInt(16).chain.assertFailed(t)
+					NewNumber(reporter, tc.value).NotInt(16).chain.assertNotFailed(t)
+				}
+			})
+		}
+	})
+}
+
+func TestNumber_IsUint(t *testing.T) {
+	t.Run("invalid argument", func(t *testing.T) {
+		reporter := newMockReporter(t)
+
+		NewNumber(reporter, 1234).IsUint(16, 32).
+			chain.assertFailed(t)
+
+		NewNumber(reporter, 1234).NotUint(16, 32).
+			chain.assertFailed(t)
+
+		NewNumber(reporter, 1234).IsUint(0).
+			chain.assertFailed(t)
+
+		NewNumber(reporter, 1234).NotUint(0).
+			chain.assertFailed(t)
+
+		NewNumber(reporter, 1234).IsUint(-16).
+			chain.assertFailed(t)
+
+		NewNumber(reporter, 1234).NotUint(-16).
+			chain.assertFailed(t)
+	})
+
+	t.Run("values", func(t *testing.T) {
+		tests := []struct {
+			name     string
+			value    float64
+			isUint16 bool
+			isUint32 bool
+			isUint   bool
+		}{
+			{
+				name:     "0",
+				value:    0,
+				isUint16: true,
+				isUint32: true,
+				isUint:   true,
+			},
+			{
+				name:     "1",
+				value:    1,
+				isUint16: true,
+				isUint32: true,
+				isUint:   true,
+			},
+			{
+				name:     "-1",
+				value:    -1,
+				isUint16: false,
+				isUint32: false,
+				isUint:   false,
+			},
+			{
+				name:     "0.5",
+				value:    0.5,
+				isUint16: false,
+				isUint32: false,
+				isUint:   false,
+			},
+			{
+				name:     "NaN",
+				value:    math.NaN(),
+				isUint16: false,
+				isUint32: false,
+				isUint:   false,
+			},
+			{
+				name:     "-Inf",
+				value:    math.Inf(-1),
+				isUint16: false,
+				isUint32: false,
+				isUint:   false,
+			},
+			{
+				name:     "+Inf",
+				value:    math.Inf(+1),
+				isUint16: false,
+				isUint32: false,
+				isUint:   false,
+			},
+			{
+				name:     "MaxUint16",
+				value:    math.MaxUint16,
+				isUint16: true,
+				isUint32: true,
+				isUint:   true,
+			},
+			{
+				name:     "MaxUint16+1",
+				value:    math.MaxUint16 + 1,
+				isUint16: false,
+				isUint32: true,
+				isUint:   true,
+			},
+			{
+				name:     "MaxUint32",
+				value:    math.MaxUint32,
+				isUint16: false,
+				isUint32: true,
+				isUint:   true,
+			},
+			{
+				name:     "MaxUint32+1",
+				value:    math.MaxUint32 + 1,
+				isUint16: false,
+				isUint32: false,
+				isUint:   true,
+			},
+		}
+
+		for _, tc := range tests {
+			t.Run(tc.name, func(t *testing.T) {
+				reporter := newMockReporter(t)
+
+				if tc.isUint {
+					NewNumber(reporter, tc.value).IsUint().chain.assertNotFailed(t)
+					NewNumber(reporter, tc.value).NotUint().chain.assertFailed(t)
+				} else {
+					NewNumber(reporter, tc.value).IsUint().chain.assertFailed(t)
+					NewNumber(reporter, tc.value).NotUint().chain.assertNotFailed(t)
+				}
+
+				if tc.isUint32 {
+					NewNumber(reporter, tc.value).IsUint(32).chain.assertNotFailed(t)
+					NewNumber(reporter, tc.value).NotUint(32).chain.assertFailed(t)
+				} else {
+					NewNumber(reporter, tc.value).IsUint(32).chain.assertFailed(t)
+					NewNumber(reporter, tc.value).NotUint(32).chain.assertNotFailed(t)
+				}
+
+				if tc.isUint16 {
+					NewNumber(reporter, tc.value).IsUint(16).chain.assertNotFailed(t)
+					NewNumber(reporter, tc.value).NotUint(16).chain.assertFailed(t)
+				} else {
+					NewNumber(reporter, tc.value).IsUint(16).chain.assertFailed(t)
+					NewNumber(reporter, tc.value).NotUint(16).chain.assertNotFailed(t)
+				}
+			})
+		}
+	})
+}
+
+func TestNumber_IsFinite(t *testing.T) {
+	tests := []struct {
+		name     string
+		value    float64
+		isFinite bool
+	}{
+		{
+			name:     "0",
+			value:    0,
+			isFinite: true,
+		},
+		{
+			name:     "1",
+			value:    1,
+			isFinite: true,
+		},
+		{
+			name:     "-1",
+			value:    -1,
+			isFinite: true,
+		},
+		{
+			name:     "0.5",
+			value:    0.5,
+			isFinite: true,
+		},
+		{
+			name:     "NaN",
+			value:    math.NaN(),
+			isFinite: false,
+		},
+		{
+			name:     "-Inf",
+			value:    math.Inf(-1),
+			isFinite: false,
+		},
+		{
+			name:     "+Inf",
+			value:    math.Inf(+1),
+			isFinite: false,
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			reporter := newMockReporter(t)
+
+			if tc.isFinite {
+				NewNumber(reporter, tc.value).IsFinite().chain.assertNotFailed(t)
+				NewNumber(reporter, tc.value).NotFinite().chain.assertFailed(t)
+			} else {
+				NewNumber(reporter, tc.value).IsFinite().chain.assertFailed(t)
+				NewNumber(reporter, tc.value).NotFinite().chain.assertNotFailed(t)
+			}
+		})
+	}
+}
diff --git a/string_test.go b/string_test.go
index c3623f2..2599d98 100644
--- a/string_test.go
+++ b/string_test.go
@@ -180,27 +180,33 @@ func TestString_Length(t *testing.T) {
 }
 
 func TestString_IsEmpty(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value1 := NewString(reporter, "")
-
-	value1.IsEmpty()
-	value1.chain.assertNotFailed(t)
-	value1.chain.clearFailed()
+	cases := map[string]struct {
+		str       string
+		wantEmpty bool
+	}{
+		"empty_string":     {str: "", wantEmpty: true},
+		"populated_string": {str: "a", wantEmpty: false},
+	}
 
-	value1.NotEmpty()
-	value1.chain.assertFailed(t)
-	value1.chain.clearFailed()
+	for name, value := range cases {
+		t.Run(name, func(t *testing.T) {
+			reporter := newMockReporter(t)
 
-	value2 := NewString(reporter, "a")
+			if value.wantEmpty {
+				NewString(reporter, value.str).IsEmpty().
+					chain.assertNotFailed(t)
 
-	value2.IsEmpty()
-	value2.chain.assertFailed(t)
-	value2.chain.clearFailed()
+				NewString(reporter, value.str).NotEmpty().
+					chain.assertFailed(t)
+			} else {
+				NewString(reporter, value.str).IsEmpty().
+					chain.assertFailed(t)
 
-	value2.NotEmpty()
-	value2.chain.assertNotFailed(t)
-	value2.chain.clearFailed()
+				NewString(reporter, value.str).NotEmpty().
+					chain.assertNotFailed(t)
+			}
+		})
+	}
 }
 
 func TestString_IsEqual(t *testing.T) {
@@ -475,164 +481,142 @@ func TestString_MatchInvalid(t *testing.T) {
 }
 
 func TestString_IsAscii(t *testing.T) {
-	reporter := newMockReporter(t)
+	cases := []struct {
+		str       string
+		wantASCII bool
+	}{
+		{"Ascii", true},
+		{string(rune(127)), true},
+		{"Ascii is アスキー", false},
+		{"アスキー", false},
+		{string(rune(128)), false},
+	}
 
-	value1 := NewString(reporter, "Ascii")
-	value1.IsASCII()
-	value1.chain.assertNotFailed(t)
-	value1.chain.clearFailed()
-
-	value2 := NewString(reporter, "Ascii is アスキー")
-	value2.IsASCII()
-	value2.chain.assertFailed(t)
-	value2.chain.clearFailed()
-
-	value3 := NewString(reporter, "アスキー")
-	value3.IsASCII()
-	value3.chain.assertFailed(t)
-	value3.chain.clearFailed()
-
-	value4 := NewString(reporter, string(rune(127)))
-	value4.IsASCII()
-	value4.chain.assertNotFailed(t)
-	value4.chain.clearFailed()
-
-	value5 := NewString(reporter, string(rune(128)))
-	value5.IsASCII()
-	value5.chain.assertFailed(t)
-	value5.chain.clearFailed()
-}
+	for _, value := range cases {
+		t.Run(value.str, func(t *testing.T) {
+			reporter := newMockReporter(t)
 
-func TestString_NotAscii(t *testing.T) {
-	reporter := newMockReporter(t)
+			if value.wantASCII {
+				NewString(reporter, value.str).IsASCII().
+					chain.assertNotFailed(t)
+
+				NewString(reporter, value.str).NotASCII().
+					chain.assertFailed(t)
+			} else {
+				NewString(reporter, value.str).IsASCII().
+					chain.assertFailed(t)
 
-	value1 := NewString(reporter, "Ascii")
-	value1.NotASCII()
-	value1.chain.assertFailed(t)
-	value1.chain.clearFailed()
-
-	value2 := NewString(reporter, "Ascii is アスキー")
-	value2.NotASCII()
-	value2.chain.assertNotFailed(t)
-	value2.chain.clearFailed()
-
-	value3 := NewString(reporter, "アスキー")
-	value3.NotASCII()
-	value3.chain.assertNotFailed(t)
-	value3.chain.clearFailed()
-
-	value4 := NewString(reporter, string(rune(127)))
-	value4.NotASCII()
-	value4.chain.assertFailed(t)
-	value4.chain.clearFailed()
-
-	value5 := NewString(reporter, string(rune(128)))
-	value5.NotASCII()
-	value5.chain.assertNotFailed(t)
-	value5.chain.clearFailed()
+				NewString(reporter, value.str).NotASCII().
+					chain.assertNotFailed(t)
+			}
+		})
+	}
 }
 
 func TestString_AsNumber(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	t.Run("default_base", func(t *testing.T) {
-		value1 := NewString(reporter, "1234567")
-		num1 := value1.AsNumber()
-		value1.chain.assertNotFailed(t)
-		num1.chain.assertNotFailed(t)
-		assert.Equal(t, float64(1234567), num1.Raw())
-
-		value2 := NewString(reporter, "11.22")
-		num2 := value2.AsNumber()
-		value2.chain.assertNotFailed(t)
-		num2.chain.assertNotFailed(t)
-		assert.Equal(t, float64(11.22), num2.Raw())
-
-		value3 := NewString(reporter, "a1")
-		num3 := value3.AsNumber()
-		value3.chain.assertFailed(t)
-		num3.chain.assertFailed(t)
-		assert.Equal(t, float64(0), num3.Raw())
-	})
-
-	t.Run("base10", func(t *testing.T) {
-		value1 := NewString(reporter, "100")
-		num1 := value1.AsNumber(10)
-		value1.chain.assertNotFailed(t)
-		num1.chain.assertNotFailed(t)
-		assert.Equal(t, float64(100), num1.Raw())
-
-		value2 := NewString(reporter, "11.22")
-		num2 := value2.AsNumber(10)
-		value2.chain.assertNotFailed(t)
-		num2.chain.assertNotFailed(t)
-		assert.Equal(t, float64(11.22), num2.Raw())
-	})
-
-	t.Run("base16", func(t *testing.T) {
-		value1 := NewString(reporter, "100")
-		num1 := value1.AsNumber(16)
-		value1.chain.assertNotFailed(t)
-		num1.chain.assertNotFailed(t)
-		assert.Equal(t, float64(0x100), num1.Raw())
-
-		value2 := NewString(reporter, "11.22")
-		num2 := value2.AsNumber(16)
-		value2.chain.assertFailed(t)
-		num2.chain.assertFailed(t)
-		assert.Equal(t, float64(0), num2.Raw())
-
-		value3 := NewString(reporter, "4000000000000000")
-		num3 := value3.AsNumber(16)
-		value3.chain.assertNotFailed(t)
-		num3.chain.assertNotFailed(t)
-		assert.Equal(t, float64(0x4000000000000000), num3.Raw())
-	})
-
-	t.Run("float_precision", func(t *testing.T) {
-		value1 := NewString(reporter, "4611686018427387905")
-		num1 := value1.AsNumber()
-		value1.chain.assertFailed(t)
-		num1.chain.assertFailed(t)
-		assert.Equal(t, float64(0), num1.Raw())
-
-		value2 := NewString(reporter, "4611686018427387905")
-		num2 := value2.AsNumber(10)
-		value2.chain.assertFailed(t)
-		num2.chain.assertFailed(t)
-		assert.Equal(t, float64(0), num2.Raw())
-
-		value3 := NewString(reporter, "8000000000000001")
-		num3 := value3.AsNumber(16)
-		value3.chain.assertFailed(t)
-		num3.chain.assertFailed(t)
-		assert.Equal(t, float64(0), num3.Raw())
-
-		value4 := NewString(reporter, "-4000000000000001")
-		num4 := value4.AsNumber(16)
-		value4.chain.assertFailed(t)
-		num4.chain.assertFailed(t)
-		assert.Equal(t, float64(0), num4.Raw())
-	})
+	cases := map[string]struct {
+		str         string
+		base        []int
+		fail        bool
+		expectedNum float64
+	}{
+		"default_base_integer": {
+			str:         "1234567",
+			fail:        false,
+			expectedNum: float64(1234567),
+		},
+		"default_base_float": {
+			str:         "11.22",
+			fail:        false,
+			expectedNum: float64(11.22),
+		},
+		"default_base_bad": {
+			str:  "a1",
+			fail: true,
+		},
+		"base10_integer": {
+			str:         "100",
+			base:        []int{10},
+			fail:        false,
+			expectedNum: float64(100),
+		},
+		"base10_float": {
+			str:         "11.22",
+			base:        []int{10},
+			fail:        false,
+			expectedNum: float64(11.22),
+		},
+		"base16_integer": {
+			str:         "100",
+			base:        []int{16},
+			fail:        false,
+			expectedNum: float64(0x100),
+		},
+		"base16_float": {
+			str:  "11.22",
+			base: []int{16},
+			fail: true,
+		},
+		"base16_large_integer": {
+			str:         "4000000000000000",
+			base:        []int{16},
+			fail:        false,
+			expectedNum: float64(0x4000000000000000),
+		},
+		"default_float_precision_max": {
+			str:  "4611686018427387905",
+			fail: true,
+		},
+		"base10_float_precision_max": {
+			str:  "4611686018427387905",
+			base: []int{10},
+			fail: true,
+		},
+		"base16_float_precision_max": {
+			str:  "8000000000000001",
+			base: []int{16},
+			fail: true,
+		},
+		"base16_float_precision_min": {
+			str:  "-4000000000000001",
+			base: []int{16},
+			fail: true,
+		},
+		"multiple_base": {
+			str:  "100",
+			base: []int{10, 16},
+			fail: true,
+		},
+	}
 
-	t.Run("multiple_base", func(t *testing.T) {
-		value1 := NewString(reporter, "100")
-		num1 := value1.AsNumber(10, 16)
-		value1.chain.assertFailed(t)
-		num1.chain.assertFailed(t)
-		assert.Equal(t, float64(0), num1.Raw())
-	})
+	for name, value := range cases {
+		t.Run(name, func(t *testing.T) {
+			reporter := newMockReporter(t)
+
+			str := NewString(reporter, value.str)
+			num := str.AsNumber(value.base...)
+
+			if value.fail {
+				str.chain.assertFailed(t)
+				num.chain.assertFailed(t)
+				assert.Equal(t, float64(0), num.Raw())
+			} else {
+				str.chain.assertNotFailed(t)
+				num.chain.assertNotFailed(t)
+				assert.Equal(t, value.expectedNum, num.Raw())
+			}
+		})
+	}
 }
 
 func TestString_AsBoolean(t *testing.T) {
-	reporter := newMockReporter(t)
-
 	trueValues := []string{"true", "True"}
 	falseValues := []string{"false", "False"}
 	badValues := []string{"TRUE", "FALSE", "t", "f", "1", "0", "bad"}
 
 	for _, str := range trueValues {
 		t.Run(str, func(t *testing.T) {
+			reporter := newMockReporter(t)
 			value := NewString(reporter, str)
 
 			b := value.AsBoolean()
@@ -644,6 +628,7 @@ func TestString_AsBoolean(t *testing.T) {
 
 	for _, str := range falseValues {
 		t.Run(str, func(t *testing.T) {
+			reporter := newMockReporter(t)
 			value := NewString(reporter, str)
 
 			b := value.AsBoolean()
@@ -655,6 +640,7 @@ func TestString_AsBoolean(t *testing.T) {
 
 	for _, str := range badValues {
 		t.Run(str, func(t *testing.T) {
+			reporter := newMockReporter(t)
 			value := NewString(reporter, str)
 
 			b := value.AsBoolean()
@@ -664,26 +650,37 @@ func TestString_AsBoolean(t *testing.T) {
 }
 
 func TestString_AsDateTime(t *testing.T) {
-	reporter := newMockReporter(t)
+	t.Run("default_formats_RFC1123+GMT", func(t *testing.T) {
+		reporter := newMockReporter(t)
+		value := NewString(reporter, "Tue, 15 Nov 1994 08:12:31 GMT")
+
+		dt := value.AsDateTime()
+		value.chain.assertNotFailed(t)
+		dt.chain.assertNotFailed(t)
+
+		assert.True(t, time.Date(1994, 11, 15, 8, 12, 31, 0, time.UTC).Equal(dt.Raw()))
+	})
+
+	t.Run("RFC822", func(t *testing.T) {
+		reporter := newMockReporter(t)
+		value := NewString(reporter, "15 Nov 94 08:12 GMT")
+
+		dt := value.AsDateTime(time.RFC822)
+		value.chain.assertNotFailed(t)
+		dt.chain.assertNotFailed(t)
+
+		assert.True(t, time.Date(1994, 11, 15, 8, 12, 0, 0, time.UTC).Equal(dt.Raw()))
+	})
+
+	t.Run("bad_input", func(t *testing.T) {
+		reporter := newMockReporter(t)
+		value := NewString(reporter, "bad")
+
+		dt := value.AsDateTime()
+		value.chain.assertFailed(t)
+		dt.chain.assertFailed(t)
 
-	t.Run("basic", func(t *testing.T) {
-		value1 := NewString(reporter, "Tue, 15 Nov 1994 08:12:31 GMT")
-		dt1 := value1.AsDateTime()
-		value1.chain.assertNotFailed(t)
-		dt1.chain.assertNotFailed(t)
-		assert.True(t, time.Date(1994, 11, 15, 8, 12, 31, 0, time.UTC).Equal(dt1.Raw()))
-
-		value2 := NewString(reporter, "15 Nov 94 08:12 GMT")
-		dt2 := value2.AsDateTime(time.RFC822)
-		value2.chain.assertNotFailed(t)
-		dt2.chain.assertNotFailed(t)
-		assert.True(t, time.Date(1994, 11, 15, 8, 12, 0, 0, time.UTC).Equal(dt2.Raw()))
-
-		value3 := NewString(reporter, "bad")
-		dt3 := value3.AsDateTime()
-		value3.chain.assertFailed(t)
-		dt3.chain.assertFailed(t)
-		assert.True(t, time.Unix(0, 0).Equal(dt3.Raw()))
+		assert.True(t, time.Unix(0, 0).Equal(dt.Raw()))
 	})
 
 	formats := []string{
@@ -701,27 +698,47 @@ func TestString_AsDateTime(t *testing.T) {
 	}
 
 	for n, f := range formats {
-		t.Run(f, func(t *testing.T) {
+		t.Run("default_formats_"+f, func(t *testing.T) {
 			str := time.Now().Format(f)
 
-			value1 := NewString(reporter, str)
-			dt1 := value1.AsDateTime()
-			dt1.chain.assertNotFailed(t)
+			reporter := newMockReporter(t)
+			value := NewString(reporter, str)
 
-			value2 := NewString(reporter, str)
-			dt2 := value2.AsDateTime(formats...)
-			dt2.chain.assertNotFailed(t)
+			dt := value.AsDateTime()
+			dt.chain.assertNotFailed(t)
+		})
 
-			value3 := NewString(reporter, str)
-			dt3 := value3.AsDateTime(f)
-			dt3.chain.assertNotFailed(t)
+		t.Run("all_formats_"+f, func(t *testing.T) {
+			str := time.Now().Format(f)
 
-			if n != 0 {
-				value4 := NewString(reporter, str)
-				dt4 := value4.AsDateTime(formats[0])
-				dt4.chain.assertFailed(t)
-			}
+			reporter := newMockReporter(t)
+			value := NewString(reporter, str)
+
+			dt := value.AsDateTime(formats...)
+			dt.chain.assertNotFailed(t)
 		})
+
+		t.Run("same_format_"+f, func(t *testing.T) {
+			str := time.Now().Format(f)
+
+			reporter := newMockReporter(t)
+			value := NewString(reporter, str)
+
+			dt := value.AsDateTime(f)
+			dt.chain.assertNotFailed(t)
+		})
+
+		if n != 0 {
+			t.Run("different_format_"+f, func(t *testing.T) {
+				str := time.Now().Format(f)
+
+				reporter := newMockReporter(t)
+				value := NewString(reporter, str)
+
+				dt := value.AsDateTime(formats[0])
+				dt.chain.assertFailed(t)
+			})
+		}
 	}
 }
 
diff --git a/value_test.go b/value_test.go
index bec29d9..8a745c9 100644
--- a/value_test.go
+++ b/value_test.go
@@ -155,7 +155,7 @@ func TestValue_Alias(t *testing.T) {
 	assert.Equal(t, []string{"foo", "Number()"}, childValue.chain.context.AliasedPath)
 }
 
-func TestValue_Casts(t *testing.T) {
+func TestValue_Getters(t *testing.T) {
 	reporter := newMockReporter(t)
 
 	t.Run("null", func(t *testing.T) {
@@ -256,119 +256,339 @@ func TestValue_Casts(t *testing.T) {
 }
 
 func TestValue_GetObject(t *testing.T) {
-	type (
-		myMap map[string]interface{}
-	)
+	type myMap map[string]interface{}
 
-	reporter := newMockReporter(t)
+	cases := map[string]struct {
+		data           interface{}
+		fail           bool
+		expectedObject map[string]interface{}
+	}{
+		"map": {
+			data:           map[string]interface{}{"foo": 123.0},
+			fail:           false,
+			expectedObject: map[string]interface{}{"foo": 123.0},
+		},
+		"myMap": {
+			data:           myMap{"foo": 123.0},
+			fail:           false,
+			expectedObject: map[string]interface{}(myMap{"foo": 123.0}),
+		},
+	}
 
-	data1 := map[string]interface{}{"foo": 123.0}
+	for name, instance := range cases {
+		t.Run(name, func(t *testing.T) {
+			reporter := newMockReporter(t)
+
+			value := NewValue(reporter, instance.data)
+			inner := value.Object()
+
+			if instance.fail {
+				inner.chain.assertNotFailed(t)
+			} else {
+				inner.chain.assertNotFailed(t)
+				assert.Equal(t, instance.expectedObject, inner.Raw())
+			}
+		})
+	}
+}
 
-	value1 := NewValue(reporter, data1)
-	inner1 := value1.Object()
+func TestValue_GetArray(t *testing.T) {
+	type myArray []interface{}
 
-	inner1.chain.assertNotFailed(t)
-	inner1.chain.clearFailed()
-	assert.Equal(t, data1, inner1.Raw())
+	cases := map[string]struct {
+		data          interface{}
+		fail          bool
+		expectedArray []interface{}
+	}{
+		"array": {
+			data:          []interface{}{"foo", 123.0},
+			fail:          false,
+			expectedArray: []interface{}{"foo", 123.0},
+		},
+		"myArray": {
+			data:          myArray{"foo", 123.0},
+			fail:          false,
+			expectedArray: []interface{}(myArray{"foo", 123.0}),
+		},
+	}
 
-	data2 := myMap{"foo": 123.0}
+	for name, instance := range cases {
+		t.Run(name, func(t *testing.T) {
+			reporter := newMockReporter(t)
 
-	value2 := NewValue(reporter, data2)
-	inner2 := value2.Object()
+			value := NewValue(reporter, instance.data)
+			inner := value.Array()
 
-	inner2.chain.assertNotFailed(t)
-	inner2.chain.clearFailed()
-	assert.Equal(t, map[string]interface{}(data2), inner2.Raw())
+			if instance.fail {
+				inner.chain.assertFailed(t)
+			} else {
+				inner.chain.assertNotFailed(t)
+				assert.Equal(t, instance.expectedArray, inner.Raw())
+			}
+		})
+	}
 }
 
-func TestValue_GetArray(t *testing.T) {
-	type (
-		myArray []interface{}
-	)
+func TestValue_GetString(t *testing.T) {
+	type myString string
 
-	reporter := newMockReporter(t)
+	cases := map[string]struct {
+		data           interface{}
+		fail           bool
+		expectedString string
+	}{
+		"string": {
+			data:           "foo",
+			fail:           false,
+			expectedString: "foo",
+		},
+		"myString": {
+			data:           myString("foo"),
+			fail:           false,
+			expectedString: "foo",
+		},
+	}
+
+	for name, instance := range cases {
+		t.Run(name, func(t *testing.T) {
+			reporter := newMockReporter(t)
+
+			value := NewValue(reporter, instance.data)
+			inner := value.String()
+
+			if instance.fail {
+				inner.chain.assertFailed(t)
+			} else {
+				inner.chain.assertNotFailed(t)
+				assert.Equal(t, instance.expectedString, inner.Raw())
+			}
+		})
+	}
+}
+
+func TestValue_GetNumber(t *testing.T) {
+	type myInt int
 
-	data1 := []interface{}{"foo", 123.0}
+	cases := map[string]struct {
+		data        interface{}
+		fail        bool
+		expectedNum float64
+	}{
+		"float":   {data: 123.0, fail: false, expectedNum: float64(123.0)},
+		"integer": {data: 123, fail: false, expectedNum: float64(123)},
+		"myInt":   {data: myInt(123), fail: false, expectedNum: float64(myInt(123))},
+	}
+
+	for name, instance := range cases {
+		t.Run(name, func(t *testing.T) {
+			reporter := newMockReporter(t)
+
+			value := NewValue(reporter, instance.data)
+			inner := value.Number()
+
+			if instance.fail {
+				inner.chain.assertFailed(t)
+			} else {
+				inner.chain.assertNotFailed(t)
+				assert.Equal(t, instance.expectedNum, inner.Raw())
+			}
+		})
+	}
+}
 
-	value1 := NewValue(reporter, data1)
-	inner1 := value1.Array()
+func TestValue_GetBoolean(t *testing.T) {
+	type myBool bool
 
-	inner1.chain.assertNotFailed(t)
-	inner1.chain.clearFailed()
-	assert.Equal(t, data1, inner1.Raw())
+	cases := map[string]struct {
+		data         interface{}
+		fail         bool
+		expectedBool bool
+	}{
+		"false":  {data: false, fail: false, expectedBool: false},
+		"true":   {data: true, fail: false, expectedBool: true},
+		"myTrue": {data: myBool(true), fail: false, expectedBool: true},
+	}
 
-	data2 := myArray{"foo", 123.0}
+	for name, instance := range cases {
+		t.Run(name, func(t *testing.T) {
+			reporter := newMockReporter(t)
 
-	value2 := NewValue(reporter, data2)
-	inner2 := value2.Array()
+			value := NewValue(reporter, instance.data)
+			inner := value.Boolean()
 
-	inner2.chain.assertNotFailed(t)
-	inner2.chain.clearFailed()
-	assert.Equal(t, []interface{}(data2), inner2.Raw())
+			if instance.fail {
+				inner.chain.assertFailed(t)
+			} else {
+				inner.chain.assertNotFailed(t)
+				assert.Equal(t, instance.expectedBool, inner.Raw())
+			}
+		})
+	}
 }
 
-func TestValue_GetString(t *testing.T) {
+func TestValue_IsObject(t *testing.T) {
 	reporter := newMockReporter(t)
 
-	value := NewValue(reporter, "foo")
-	inner := value.String()
+	cases := map[string]struct {
+		data       interface{}
+		wantObject bool
+	}{
+		"object": {data: map[string]interface{}{"foo": 123.0}, wantObject: true},
+		"string": {data: "foo", wantObject: false},
+	}
+
+	for name, instance := range cases {
+		t.Run(name, func(t *testing.T) {
+			value := NewValue(reporter, instance.data)
+
+			if instance.wantObject {
+				value.IsObject()
+				value.chain.assertNotFailed(t)
 
-	inner.chain.assertNotFailed(t)
-	inner.chain.clearFailed()
-	assert.Equal(t, "foo", inner.Raw())
+				value.NotObject()
+				value.chain.assertFailed(t)
+			} else {
+				value.IsObject()
+				value.chain.assertFailed(t)
+				value.chain.clearFailed()
+
+				value.NotObject()
+				value.chain.assertNotFailed(t)
+			}
+		})
+	}
 }
 
-func TestValue_GetNumber(t *testing.T) {
-	type (
-		myInt int
-	)
+func TestValue_IsArray(t *testing.T) {
+	reporter := newMockReporter(t)
+
+	cases := map[string]struct {
+		data      interface{}
+		wantArray bool
+	}{
+		"array":  {data: []interface{}{"foo", "123"}, wantArray: true},
+		"string": {data: "foo", wantArray: false},
+	}
+
+	for name, instance := range cases {
+		t.Run(name, func(t *testing.T) {
+			value := NewValue(reporter, instance.data)
+
+			if instance.wantArray {
+				value.IsArray()
+				value.chain.assertNotFailed(t)
+
+				value.NotArray()
+				value.chain.assertFailed(t)
+			} else {
+				value.IsArray()
+				value.chain.assertFailed(t)
+				value.chain.clearFailed()
+
+				value.NotArray()
+				value.chain.assertNotFailed(t)
+			}
+		})
+	}
+}
 
+func TestValue_IsString(t *testing.T) {
 	reporter := newMockReporter(t)
 
-	data1 := 123.0
+	cases := map[string]struct {
+		data       interface{}
+		wantString bool
+	}{
+		"string":  {data: "foo", wantString: true},
+		"integer": {data: 123, wantString: false},
+	}
+
+	for name, instance := range cases {
+		t.Run(name, func(t *testing.T) {
+			value := NewValue(reporter, instance.data)
+
+			if instance.wantString {
+				value.IsString()
+				value.chain.assertNotFailed(t)
 
-	value1 := NewValue(reporter, data1)
-	inner1 := value1.Number()
+				value.NotString()
+				value.chain.assertFailed(t)
+			} else {
+				value.IsString()
+				value.chain.assertFailed(t)
+				value.chain.clearFailed()
 
-	inner1.chain.assertNotFailed(t)
-	inner1.chain.clearFailed()
-	assert.Equal(t, data1, inner1.Raw())
+				value.NotString()
+				value.chain.assertNotFailed(t)
+			}
+		})
+	}
+}
 
-	data2 := 123
+func TestValue_IsNumber(t *testing.T) {
+	reporter := newMockReporter(t)
 
-	value2 := NewValue(reporter, data2)
-	inner2 := value2.Number()
+	cases := map[string]struct {
+		data       interface{}
+		wantNumber bool
+	}{
+		"integer": {data: 123, wantNumber: true},
+		"string":  {data: "foo", wantNumber: false},
+	}
 
-	inner2.chain.assertNotFailed(t)
-	inner2.chain.clearFailed()
-	assert.Equal(t, float64(data2), inner2.Raw())
+	for name, instance := range cases {
+		t.Run(name, func(t *testing.T) {
+			value := NewValue(reporter, instance.data)
 
-	data3 := myInt(123)
+			if instance.wantNumber {
+				value.IsNumber()
+				value.chain.assertNotFailed(t)
 
-	value3 := NewValue(reporter, data3)
-	inner3 := value3.Number()
+				value.NotNumber()
+				value.chain.assertFailed(t)
+			} else {
+				value.IsNumber()
+				value.chain.assertFailed(t)
+				value.chain.clearFailed()
 
-	inner3.chain.assertNotFailed(t)
-	inner3.chain.clearFailed()
-	assert.Equal(t, float64(data3), inner3.Raw())
+				value.NotNumber()
+				value.chain.assertNotFailed(t)
+			}
+		})
+	}
 }
 
-func TestValue_GetBoolean(t *testing.T) {
+func TestValue_IsBoolean(t *testing.T) {
 	reporter := newMockReporter(t)
 
-	value1 := NewValue(reporter, true)
-	inner1 := value1.Boolean()
+	cases := map[string]struct {
+		data     interface{}
+		wantBool bool
+	}{
+		"bool":   {data: true, wantBool: true},
+		"string": {data: "foo", wantBool: false},
+	}
+
+	for name, instance := range cases {
+		t.Run(name, func(t *testing.T) {
+			value := NewValue(reporter, instance.data)
 
-	inner1.chain.assertNotFailed(t)
-	inner1.chain.clearFailed()
-	assert.Equal(t, true, inner1.Raw())
+			if instance.wantBool {
+				value.IsBoolean()
+				value.chain.assertNotFailed(t)
 
-	value2 := NewValue(reporter, false)
-	inner2 := value2.Boolean()
+				value.NotBoolean()
+				value.chain.assertFailed(t)
+			} else {
+				value.IsBoolean()
+				value.chain.assertFailed(t)
+				value.chain.clearFailed()
 
-	inner2.chain.assertNotFailed(t)
-	inner2.chain.clearFailed()
-	assert.Equal(t, false, inner2.Raw())
+				value.NotBoolean()
+				value.chain.assertNotFailed(t)
+			}
+		})
+	}
 }
 
 func TestValue_IsEqual(t *testing.T) {
@@ -797,107 +1017,3 @@ func TestValue_Schema(t *testing.T) {
 	NewValue(reporter, data1).Schema("file:///bad/path").chain.assertFailed(t)
 	NewValue(reporter, data1).Schema("{ bad json").chain.assertFailed(t)
 }
-
-func TestValue_IsObject(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	data := map[string]interface{}{"foo": 123.0}
-
-	value1 := NewValue(reporter, data)
-	value1.IsObject()
-	value1.chain.assertNotFailed(t)
-
-	value1.NotObject()
-	value1.chain.assertFailed(t)
-	value1.chain.clearFailed()
-
-	value2 := NewValue(reporter, "foo")
-	value2.IsObject()
-	value2.chain.assertFailed(t)
-	value2.chain.clearFailed()
-
-	value2.NotObject()
-	value2.chain.assertNotFailed(t)
-}
-
-func TestValue_IsArray(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	data := []interface{}{"foo", "123"}
-
-	value1 := NewValue(reporter, data)
-	value1.IsArray()
-	value1.chain.assertNotFailed(t)
-
-	value1.NotArray()
-	value1.chain.assertFailed(t)
-	value1.chain.clearFailed()
-
-	value2 := NewValue(reporter, "foo")
-	value2.IsArray()
-	value2.chain.assertFailed(t)
-	value2.chain.clearFailed()
-
-	value2.NotArray()
-	value2.chain.assertNotFailed(t)
-}
-
-func TestValue_IsString(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value1 := NewValue(reporter, "foo")
-	value1.IsString()
-	value1.chain.assertNotFailed(t)
-
-	value1.NotString()
-	value1.chain.assertFailed(t)
-	value1.chain.clearFailed()
-
-	value2 := NewValue(reporter, 123)
-	value2.IsString()
-	value2.chain.assertFailed(t)
-	value2.chain.clearFailed()
-
-	value2.NotString()
-	value2.chain.assertNotFailed(t)
-}
-
-func TestValue_IsNumber(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value1 := NewValue(reporter, 123)
-	value1.IsNumber()
-	value1.chain.assertNotFailed(t)
-
-	value1.NotNumber()
-	value1.chain.assertFailed(t)
-	value1.chain.clearFailed()
-
-	value2 := NewValue(reporter, "foo")
-	value2.IsNumber()
-	value2.chain.assertFailed(t)
-	value2.chain.clearFailed()
-
-	value2.NotNumber()
-	value2.chain.assertNotFailed(t)
-}
-
-func TestValue_IsBoolean(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value1 := NewValue(reporter, true)
-	value1.IsBoolean()
-	value1.chain.assertNotFailed(t)
-
-	value1.NotBoolean()
-	value1.chain.assertFailed(t)
-	value1.chain.clearFailed()
-
-	value2 := NewValue(reporter, "foo")
-	value2.IsBoolean()
-	value2.chain.assertFailed(t)
-	value2.chain.clearFailed()
-
-	value2.NotBoolean()
-	value2.chain.assertNotFailed(t)
-}