Repository: gavv/httpexpect (go)

Issue:
Add Value.IsXxx
`Value` has several methods to cast value to specific type:

* Object
* Array
* String
* Number
* Boolean

Each of them checks that the value has correct type and returns a new object for the value.

For each of these methods, we should add a counterpart method `IsXxx` (e.g. IsObject for Object) which just checks that the value has specific type, but does not create a new object, but instead returns the original (receiver) object.

New methods should have documentation comments and unit tests.

Related: https://github.com/gavv/httpexpect/issues/252

Pull request description:
Add Value.IsXxx
Closes #253 

Patch:
diff --git a/value.go b/value.go
index dcb1b0c..cae9355 100644
--- a/value.go
+++ b/value.go
@@ -624,3 +624,295 @@ func (v *Value) NotInList(values ...interface{}) *Value {
 
 	return v
 }
+
+// IsObject succeeds if the underlying value is an object.
+//
+// If underlying value is not an object (map[string]interface{}), failure is reported.
+//
+// Example:
+//
+//	value := NewValue(t, map[string]interface{}{"foo": 123})
+//	value.IsObject()
+func (v *Value) IsObject() *Value {
+	opChain := v.chain.enter("IsObject()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return v
+	}
+
+	if _, ok := v.value.(map[string]interface{}); !ok {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{v.value},
+			Errors: []error{
+				errors.New("expected: value is object"),
+			},
+		})
+	}
+
+	return v
+}
+
+// NotObject succeeds if the underlying value is not an object.
+//
+// If underlying value is an object (map[string]interface{}), failure is reported.
+//
+// Example:
+//
+//	value := NewValue(t, map[string]interface{}{"foo": 123})
+//	value.NotObject()
+func (v *Value) NotObject() *Value {
+	opChain := v.chain.enter("NotObject()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return v
+	}
+
+	if _, ok := v.value.(map[string]interface{}); ok {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{v.value},
+			Errors: []error{
+				errors.New("expected: value is not object"),
+			},
+		})
+	}
+
+	return v
+}
+
+// IsArray succeeds if the underlying value is an array.
+//
+// If underlying value is not an array ([]interface{}), failure is reported.
+//
+// Example:
+//
+//	value := NewValue(t, []interface{}{"foo", "123"})
+//	value.IsArray()
+func (v *Value) IsArray() *Value {
+	opChain := v.chain.enter("IsArray()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return v
+	}
+
+	if _, ok := v.value.([]interface{}); !ok {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{v.value},
+			Errors: []error{
+				errors.New("expected: value is array"),
+			},
+		})
+	}
+
+	return v
+}
+
+// NotArray succeeds if the underlying value is not an array.
+//
+// If underlying value is an array ([]interface{}), failure is reported.
+//
+// Example:
+//
+//	value := NewValue(t, []interface{}{"foo", "123"})
+//	value.NotArray()
+func (v *Value) NotArray() *Value {
+	opChain := v.chain.enter("NotArray()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return v
+	}
+
+	if _, ok := v.value.([]interface{}); ok {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{v.value},
+			Errors: []error{
+				errors.New("expected: value is not array"),
+			},
+		})
+	}
+
+	return v
+}
+
+// IsString succeeds if the underlying value is a string.
+//
+// If underlying value is not a string, failure is reported.
+//
+// Example:
+//
+//	value := NewValue(t, "foo")
+//	value.IsString()
+func (v *Value) IsString() *Value {
+	opChain := v.chain.enter("IsString()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return v
+	}
+
+	if _, ok := v.value.(string); !ok {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{v.value},
+			Errors: []error{
+				errors.New("expected: value is string"),
+			},
+		})
+	}
+
+	return v
+}
+
+// NotString succeeds if the underlying value is not a string.
+//
+// If underlying value is a string, failure is reported.
+//
+// Example:
+//
+//	value := NewValue(t, "foo")
+//	value.NotString()
+func (v *Value) NotString() *Value {
+	opChain := v.chain.enter("NotString()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return v
+	}
+
+	if _, ok := v.value.(string); ok {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{v.value},
+			Errors: []error{
+				errors.New("expected: value is not string"),
+			},
+		})
+	}
+
+	return v
+}
+
+// IsNumber succeeds if the underlying value is a number.
+//
+// If underlying value is not a number (numeric type convertible to float64),
+// failure is reported
+//
+// Example:
+//
+//	value := NewValue(t, 123)
+//	value.IsNumber()
+func (v *Value) IsNumber() *Value {
+	opChain := v.chain.enter("IsNumber()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return v
+	}
+
+	if _, ok := v.value.(float64); !ok {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{v.value},
+			Errors: []error{
+				errors.New("expected: value is number"),
+			},
+		})
+	}
+
+	return v
+}
+
+// NotNumber succeeds if the underlying value is a not a number.
+//
+// If underlying value is a number (numeric type convertible to float64),
+// failure is reported
+//
+// Example:
+//
+//	value := NewValue(t, 123)
+//	value.NotNumber()
+func (v *Value) NotNumber() *Value {
+	opChain := v.chain.enter("NotNumber()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return v
+	}
+
+	if _, ok := v.value.(float64); ok {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{v.value},
+			Errors: []error{
+				errors.New("expected: value is not number"),
+			},
+		})
+	}
+
+	return v
+}
+
+// IsBoolean succeeds if the underlying value is a boolean.
+//
+// If underlying value is not a boolean, failure is reported.
+//
+// Example:
+//
+//	value := NewValue(t, true)
+//	value.IsBoolean()
+func (v *Value) IsBoolean() *Value {
+	opChain := v.chain.enter("IsBoolean()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return v
+	}
+
+	if _, ok := v.value.(bool); !ok {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{v.value},
+			Errors: []error{
+				errors.New("expected: value is boolean"),
+			},
+		})
+	}
+
+	return v
+}
+
+// NotBoolean succeeds if the underlying value is not a boolean.
+//
+// If underlying value is a boolean, failure is reported.
+//
+// Example:
+//
+//	value := NewValue(t, true)
+//	value.NotBoolean()
+func (v *Value) NotBoolean() *Value {
+	opChain := v.chain.enter("NotBoolean()")
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return v
+	}
+
+	if _, ok := v.value.(bool); ok {
+		opChain.fail(AssertionFailure{
+			Type:   AssertValid,
+			Actual: &AssertionValue{v.value},
+			Errors: []error{
+				errors.New("expected: value is not boolean"),
+			},
+		})
+	}
+
+	return v
+}


Test patch:
diff --git a/value_test.go b/value_test.go
index 5d0e610..da465dd 100644
--- a/value_test.go
+++ b/value_test.go
@@ -45,6 +45,18 @@ func TestValue_Failed(t *testing.T) {
 
 	value.InList(nil)
 	value.NotInList(nil)
+
+	value.IsObject().chain.assertFailed(t)
+	value.IsArray().chain.assertFailed(t)
+	value.IsString().chain.assertFailed(t)
+	value.IsNumber().chain.assertFailed(t)
+	value.IsBoolean().chain.assertFailed(t)
+
+	value.NotObject().chain.assertFailed(t)
+	value.NotArray().chain.assertFailed(t)
+	value.NotString().chain.assertFailed(t)
+	value.NotNumber().chain.assertFailed(t)
+	value.NotBoolean().chain.assertFailed(t)
 }
 
 func TestValue_Constructors(t *testing.T) {
@@ -814,3 +826,107 @@ 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)
+}