Repository: gavv/httpexpect
Pull request #350 (2023-03-28 18:19:10)

TestRequest_Websocket
#329 


Patch:
diff --git a/chain.go b/chain.go
index 18926ac..ab64ef0 100644
--- a/chain.go
+++ b/chain.go
@@ -139,7 +139,7 @@ func newChainWithConfig(name string, config Config) *chain {
 }
 
 // Construct chain using DefaultAssertionHandler and provided Reporter.
-func newChainWithDefaults(name string, reporter Reporter) *chain {
+func newChainWithDefaults(name string, reporter Reporter, flag ...chainFlags) *chain {
 	if reporter == nil {
 		panic("Reporter is nil")
 	}
@@ -165,6 +165,10 @@ func newChainWithDefaults(name string, reporter Reporter) *chain {
 
 	c.context.TestingTB = isTestingTB(c.handler)
 
+	for _, f := range flag {
+		c.flags |= f
+	}
+
 	return c
 }
 
@@ -450,29 +454,9 @@ func (c *chain) treeFailed() bool {
 	return c.flags&(flagFailed|flagFailedChildren) != 0
 }
 
-// DEPRECATED: use setFlags
-func (c *chain) setFailed() {
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
-	c.flags |= flagFailed
-}
-
-// Set chain flags.
-// For tests.
-func (c *chain) setFlags(flags chainFlags) {
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
-	c.flags = flags
-}
-
 // DEPRECATED: use clear
 func (c *chain) clearFailed() {
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
-	c.flags &= ^(flagFailed | flagFailedChildren)
+	c.clear()
 }
 
 // Clear failure flags.
@@ -486,26 +470,12 @@ func (c *chain) clear() {
 
 // DEPRECATED: use assert
 func (c *chain) assertNotFailed(t testing.TB) {
-	c.assertOK(t, true)
+	c.assert(t, success)
 }
 
 // DEPRECATED: use assert
 func (c *chain) assertFailed(t testing.TB) {
-	c.assertOK(t, false)
-}
-
-// DEPRECATED: use assert
-func (c *chain) assertOK(t testing.TB, expectOK bool) {
-	c.mu.Lock()
-	defer c.mu.Unlock()
-
-	if expectOK {
-		assert.Equal(t, chainFlags(0), c.flags&flagFailed,
-			"expected: chain is ok")
-	} else {
-		assert.NotEqual(t, chainFlags(0), c.flags&flagFailed,
-			"expected: chain is not ok")
-	}
+	c.assert(t, failure)
 }
 
 // Report failure unless chain has specified state.
diff --git a/request.go b/request.go
index db81bd0..74e845f 100644
--- a/request.go
+++ b/request.go
@@ -49,9 +49,10 @@ type Request struct {
 	path    string
 	query   url.Values
 
-	form      url.Values
-	formbuf   *bytes.Buffer
-	multipart *multipart.Writer
+	form        url.Values
+	formbuf     *bytes.Buffer
+	multipart   *multipart.Writer
+	multipartFn func(w io.Writer) *multipart.Writer
 
 	bodySetter   string
 	typeSetter   string
@@ -127,6 +128,9 @@ func newRequest(
 		sleepFn: func(d time.Duration) <-chan time.Time {
 			return time.After(d)
 		},
+		multipartFn: func(w io.Writer) *multipart.Writer {
+			return multipart.NewWriter(w)
+		},
 	}
 
 	opChain := r.chain.enter("")
@@ -1922,7 +1926,7 @@ func (r *Request) WithMultipart() *Request {
 
 	if r.multipart == nil {
 		r.formbuf = &bytes.Buffer{}
-		r.multipart = multipart.NewWriter(r.formbuf)
+		r.multipart = r.multipartFn(r.formbuf)
 		r.setBody(opChain, "WithMultipart()", r.formbuf, 0, false)
 	}
 


Test patch:
diff --git a/array_test.go b/array_test.go
index 4f95101..8fe9b1c 100644
--- a/array_test.go
+++ b/array_test.go
@@ -72,7 +72,7 @@ func TestArray_FailedChain(t *testing.T) {
 	}
 
 	t.Run("failed chain", func(t *testing.T) {
-		chain := newFailedChain(t)
+		chain := newMockChain(t, flagFailed)
 		value := newArray(chain, []interface{}{})
 
 		check(value)
@@ -86,7 +86,7 @@ func TestArray_FailedChain(t *testing.T) {
 	})
 
 	t.Run("failed chain, nil value", func(t *testing.T) {
-		chain := newFailedChain(t)
+		chain := newMockChain(t, flagFailed)
 		value := newArray(chain, nil)
 
 		check(value)
diff --git a/boolean_test.go b/boolean_test.go
index 588b572..5b9ce70 100644
--- a/boolean_test.go
+++ b/boolean_test.go
@@ -8,7 +8,7 @@ import (
 )
 
 func TestBoolean_FailedChain(t *testing.T) {
-	chain := newFailedChain(t)
+	chain := newMockChain(t, flagFailed)
 
 	value := newBoolean(chain, false)
 	value.chain.assert(t, failure)
diff --git a/canon_test.go b/canon_test.go
index 2fdc8ff..749a082 100644
--- a/canon_test.go
+++ b/canon_test.go
@@ -22,30 +22,30 @@ func TestCanon_Number(t *testing.T) {
 	val, ok = canonNumber(chain, 123)
 	assert.True(t, ok)
 	assert.Equal(t, 123.0, val)
-	chain.assertNotFailed(t)
-	chain.clearFailed()
+	chain.assert(t, success)
+	chain.clear()
 
 	val, ok = canonNumber(chain, 123.0)
 	assert.True(t, ok)
 	assert.Equal(t, 123.0, val)
-	chain.assertNotFailed(t)
-	chain.clearFailed()
+	chain.assert(t, success)
+	chain.clear()
 
 	val, ok = canonNumber(chain, myInt(123))
 	assert.True(t, ok)
 	assert.Equal(t, 123.0, val)
-	chain.assertNotFailed(t)
-	chain.clearFailed()
+	chain.assert(t, success)
+	chain.clear()
 
 	_, ok = canonNumber(chain, "123")
 	assert.False(t, ok)
-	chain.assertFailed(t)
-	chain.clearFailed()
+	chain.assert(t, failure)
+	chain.clear()
 
 	_, ok = canonNumber(chain, nil)
 	assert.False(t, ok)
-	chain.assertFailed(t)
-	chain.clearFailed()
+	chain.assert(t, failure)
+	chain.clear()
 }
 
 func TestCanon_Array(t *testing.T) {
@@ -65,34 +65,34 @@ func TestCanon_Array(t *testing.T) {
 	val, ok = canonArray(chain, []interface{}{123.0, 456.0})
 	assert.True(t, ok)
 	assert.Equal(t, []interface{}{123.0, 456.0}, val)
-	chain.assertNotFailed(t)
-	chain.clearFailed()
+	chain.assert(t, success)
+	chain.clear()
 
 	val, ok = canonArray(chain, myArray{myInt(123), 456.0})
 	assert.True(t, ok)
 	assert.Equal(t, []interface{}{123.0, 456.0}, val)
-	chain.assertNotFailed(t)
-	chain.clearFailed()
+	chain.assert(t, success)
+	chain.clear()
 
 	_, ok = canonArray(chain, "123")
 	assert.False(t, ok)
-	chain.assertFailed(t)
-	chain.clearFailed()
+	chain.assert(t, failure)
+	chain.clear()
 
 	_, ok = canonArray(chain, func() {})
 	assert.False(t, ok)
-	chain.assertFailed(t)
-	chain.clearFailed()
+	chain.assert(t, failure)
+	chain.clear()
 
 	_, ok = canonArray(chain, nil)
 	assert.False(t, ok)
-	chain.assertFailed(t)
-	chain.clearFailed()
+	chain.assert(t, failure)
+	chain.clear()
 
 	_, ok = canonArray(chain, []interface{}(nil))
 	assert.False(t, ok)
-	chain.assertFailed(t)
-	chain.clearFailed()
+	chain.assert(t, failure)
+	chain.clear()
 }
 
 func TestCanon_Map(t *testing.T) {
@@ -112,34 +112,34 @@ func TestCanon_Map(t *testing.T) {
 	val, ok = canonMap(chain, map[string]interface{}{"foo": 123.0})
 	assert.True(t, ok)
 	assert.Equal(t, map[string]interface{}{"foo": 123.0}, val)
-	chain.assertNotFailed(t)
-	chain.clearFailed()
+	chain.assert(t, success)
+	chain.clear()
 
 	val, ok = canonMap(chain, myMap{"foo": myInt(123)})
 	assert.True(t, ok)
 	assert.Equal(t, map[string]interface{}{"foo": 123.0}, val)
-	chain.assertNotFailed(t)
-	chain.clearFailed()
+	chain.assert(t, success)
+	chain.clear()
 
 	_, ok = canonMap(chain, "123")
 	assert.False(t, ok)
-	chain.assertFailed(t)
-	chain.clearFailed()
+	chain.assert(t, failure)
+	chain.clear()
 
 	_, ok = canonMap(chain, func() {})
 	assert.False(t, ok)
-	chain.assertFailed(t)
-	chain.clearFailed()
+	chain.assert(t, failure)
+	chain.clear()
 
 	_, ok = canonMap(chain, nil)
 	assert.False(t, ok)
-	chain.assertFailed(t)
-	chain.clearFailed()
+	chain.assert(t, failure)
+	chain.clear()
 
 	_, ok = canonMap(chain, map[string]interface{}(nil))
 	assert.False(t, ok)
-	chain.assertFailed(t)
-	chain.clearFailed()
+	chain.assert(t, failure)
+	chain.clear()
 }
 
 func TestCannon_Decode(t *testing.T) {
@@ -149,7 +149,7 @@ func TestCannon_Decode(t *testing.T) {
 
 		canonDecode(chain, 123, nil)
 
-		chain.assertFailed(t)
+		chain.assert(t, failure)
 	})
 
 	t.Run("value is not marshallable", func(t *testing.T) {
@@ -167,7 +167,7 @@ func TestCannon_Decode(t *testing.T) {
 		var target S
 		canonDecode(chain, value, &target)
 
-		chain.assertFailed(t)
+		chain.assert(t, failure)
 	})
 
 	t.Run("value is not unmarshallable into target", func(t *testing.T) {
@@ -177,6 +177,6 @@ func TestCannon_Decode(t *testing.T) {
 		var target int
 		canonDecode(chain, true, target)
 
-		chain.assertFailed(t)
+		chain.assert(t, failure)
 	})
 }
diff --git a/cookie_test.go b/cookie_test.go
index a115386..41ce757 100644
--- a/cookie_test.go
+++ b/cookie_test.go
@@ -33,7 +33,7 @@ func TestCookie_FailedChain(t *testing.T) {
 	}
 
 	t.Run("failed chain", func(t *testing.T) {
-		chain := newFailedChain(t)
+		chain := newMockChain(t, flagFailed)
 		value := newCookie(chain, &http.Cookie{})
 
 		check(value, false)
@@ -47,7 +47,7 @@ func TestCookie_FailedChain(t *testing.T) {
 	})
 
 	t.Run("failed chain, nil value", func(t *testing.T) {
-		chain := newFailedChain(t)
+		chain := newMockChain(t, flagFailed)
 		value := newCookie(chain, nil)
 
 		check(value, true)
diff --git a/datetime_test.go b/datetime_test.go
index cc9ff17..a573cfa 100644
--- a/datetime_test.go
+++ b/datetime_test.go
@@ -8,7 +8,7 @@ import (
 )
 
 func TestDateTime_FailedChain(t *testing.T) {
-	chain := newFailedChain(t)
+	chain := newMockChain(t, flagFailed)
 
 	tm := time.Unix(0, 0)
 	value := newDateTime(chain, tm)
diff --git a/duration_test.go b/duration_test.go
index 4867f30..8e25e8e 100644
--- a/duration_test.go
+++ b/duration_test.go
@@ -8,7 +8,7 @@ import (
 )
 
 func TestDuration_FailedChain(t *testing.T) {
-	chain := newFailedChain(t)
+	chain := newMockChain(t, flagFailed)
 
 	tm := time.Second
 	value := newDuration(chain, &tm)
diff --git a/match_test.go b/match_test.go
index 2d74261..db92a1e 100644
--- a/match_test.go
+++ b/match_test.go
@@ -7,7 +7,7 @@ import (
 )
 
 func TestMatch_FailedChain(t *testing.T) {
-	chain := newFailedChain(t)
+	chain := newMockChain(t, flagFailed)
 
 	value := newMatch(chain, nil, nil)
 	value.chain.assert(t, failure)
diff --git a/mocks_test.go b/mocks_test.go
index 80bcb96..20e6cb6 100644
--- a/mocks_test.go
+++ b/mocks_test.go
@@ -147,14 +147,8 @@ func newMockConfig(r Reporter) Config {
 	return Config{Reporter: r}.withDefaults()
 }
 
-func newMockChain(t *testing.T) *chain {
-	return newChainWithDefaults("test", newMockReporter(t))
-}
-
-func newFailedChain(t *testing.T) *chain {
-	chain := newMockChain(t)
-	chain.setFlags(flagFailed)
-	return chain
+func newMockChain(t *testing.T, flag ...chainFlags) *chain {
+	return newChainWithDefaults("test", newMockReporter(t), flag...)
 }
 
 type mockLogger struct {
@@ -334,3 +328,16 @@ type mockError struct{}
 func (e *mockError) Error() string {
 	return ""
 }
+
+type mockWriter struct {
+	io.Writer
+	err error
+}
+
+func (w *mockWriter) Write(p []byte) (n int, err error) {
+	if w.err != nil {
+		return 0, err
+	}
+
+	return w.Writer.Write(p)
+}
diff --git a/number_test.go b/number_test.go
index ca845fd..475ab9c 100644
--- a/number_test.go
+++ b/number_test.go
@@ -8,13 +8,12 @@ import (
 )
 
 func TestNumber_FailedChain(t *testing.T) {
-	chain := newMockChain(t)
-	chain.setFailed()
+	chain := newMockChain(t, flagFailed)
 
 	value := newNumber(chain, 0)
-	value.chain.assertFailed(t)
+	value.chain.assert(t, failure)
 
-	value.Path("$").chain.assertFailed(t)
+	value.Path("$").chain.assert(t, failure)
 	value.Schema("")
 	value.Alias("foo")
 
@@ -46,7 +45,7 @@ func TestNumber_Constructors(t *testing.T) {
 		reporter := newMockReporter(t)
 		value := NewNumber(reporter, 10.3)
 		value.IsEqual(10.3)
-		value.chain.assertNotFailed(t)
+		value.chain.assert(t, success)
 	})
 
 	t.Run("config", func(t *testing.T) {
@@ -55,7 +54,7 @@ func TestNumber_Constructors(t *testing.T) {
 			Reporter: reporter,
 		}, 10.3)
 		value.IsEqual(10.3)
-		value.chain.assertNotFailed(t)
+		value.chain.assert(t, success)
 	})
 
 	t.Run("chain", func(t *testing.T) {
@@ -75,7 +74,7 @@ func TestNumber_Decode(t *testing.T) {
 		var target interface{}
 		value.Decode(&target)
 
-		value.chain.assertNotFailed(t)
+		value.chain.assert(t, success)
 		assert.Equal(t, 10.1, target)
 	})
 
@@ -87,7 +86,7 @@ func TestNumber_Decode(t *testing.T) {
 		var target int
 		value.Decode(&target)
 
-		value.chain.assertNotFailed(t)
+		value.chain.assert(t, success)
 		assert.Equal(t, 10, target)
 	})
 
@@ -99,7 +98,7 @@ func TestNumber_Decode(t *testing.T) {
 		var target float64
 		value.Decode(&target)
 
-		value.chain.assertNotFailed(t)
+		value.chain.assert(t, success)
 		assert.Equal(t, 10.1, target)
 	})
 
@@ -110,7 +109,7 @@ func TestNumber_Decode(t *testing.T) {
 
 		value.Decode(nil)
 
-		value.chain.assertFailed(t)
+		value.chain.assert(t, failure)
 	})
 
 	t.Run("target is unmarshable", func(t *testing.T) {
@@ -120,7 +119,7 @@ func TestNumber_Decode(t *testing.T) {
 
 		value.Decode(123)
 
-		value.chain.assertFailed(t)
+		value.chain.assert(t, failure)
 	})
 }
 
@@ -142,53 +141,53 @@ func TestNumber_Getters(t *testing.T) {
 	value := NewNumber(reporter, 123.0)
 
 	assert.Equal(t, 123.0, value.Raw())
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	value.chain.assert(t, success)
+	value.chain.clear()
 
 	assert.Equal(t, 123.0, value.Path("$").Raw())
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	value.chain.assert(t, success)
+	value.chain.clear()
 
 	value.Schema(`{"type": "number"}`)
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	value.chain.assert(t, success)
+	value.chain.clear()
 
 	value.Schema(`{"type": "object"}`)
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	value.chain.assert(t, failure)
+	value.chain.clear()
 }
 
 func TestNumber_IsEqual(t *testing.T) {
 	t.Run("basic", func(t *testing.T) {
 		cases := []struct {
-			name    string
-			number  float64
-			value   interface{}
-			isEqual bool
+			name      string
+			number    float64
+			value     interface{}
+			wantEqual chainResult
 		}{
 			{
-				name:    "compare equivalent integers",
-				number:  1234,
-				value:   1234,
-				isEqual: true,
+				name:      "compare equivalent integers",
+				number:    1234,
+				value:     1234,
+				wantEqual: success,
 			},
 			{
-				name:    "compare non-equivalent integers",
-				number:  1234,
-				value:   4321,
-				isEqual: false,
+				name:      "compare non-equivalent integers",
+				number:    1234,
+				value:     4321,
+				wantEqual: failure,
 			},
 			{
-				name:    "compare NaN to float",
-				number:  math.NaN(),
-				value:   1234.5,
-				isEqual: false,
+				name:      "compare NaN to float",
+				number:    math.NaN(),
+				value:     1234.5,
+				wantEqual: failure,
 			},
 			{
-				name:    "compare float to NaN",
-				number:  1234.5,
-				value:   math.NaN(),
-				isEqual: false,
+				name:      "compare float to NaN",
+				number:    1234.5,
+				value:     math.NaN(),
+				wantEqual: failure,
 			},
 		}
 
@@ -196,23 +195,11 @@ func TestNumber_IsEqual(t *testing.T) {
 			t.Run(tc.name, func(t *testing.T) {
 				reporter := newMockReporter(t)
 
-				if tc.isEqual {
-					NewNumber(reporter, tc.number).
-						IsEqual(tc.value).
-						chain.assertNotFailed(t)
-
-					NewNumber(reporter, tc.number).
-						NotEqual(tc.value).
-						chain.assertFailed(t)
-				} else {
-					NewNumber(reporter, tc.number).
-						NotEqual(tc.value).
-						chain.assertNotFailed(t)
-
-					NewNumber(reporter, tc.number).
-						IsEqual(tc.value).
-						chain.assertFailed(t)
-				}
+				NewNumber(reporter, tc.number).IsEqual(tc.value).
+					chain.assert(t, tc.wantEqual)
+
+				NewNumber(reporter, tc.number).NotEqual(tc.value).
+					chain.assert(t, !tc.wantEqual)
 			})
 		}
 	})
@@ -221,93 +208,93 @@ func TestNumber_IsEqual(t *testing.T) {
 		reporter := newMockReporter(t)
 
 		NewNumber(reporter, 1234).IsEqual(int64(1234)).
-			chain.assertNotFailed(t)
+			chain.assert(t, success)
 
 		NewNumber(reporter, 1234).IsEqual(float32(1234)).
-			chain.assertNotFailed(t)
+			chain.assert(t, success)
 
 		NewNumber(reporter, 1234).NotEqual(int64(4321)).
-			chain.assertNotFailed(t)
+			chain.assert(t, success)
 
 		NewNumber(reporter, 1234).NotEqual(float32(4321)).
-			chain.assertNotFailed(t)
+			chain.assert(t, success)
 	})
 
 	t.Run("invalid argument", func(t *testing.T) {
 		reporter := newMockReporter(t)
 
 		NewNumber(reporter, 1234).IsEqual("NOT NUMBER").
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 1234).NotEqual("NOT NUMBER").
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 	})
 }
 
 func TestNumber_InDelta(t *testing.T) {
 	cases := []struct {
-		name         string
-		number       float64
-		value        float64
-		delta        float64
-		isInDelta    bool
-		isNotInDelta bool
+		name           string
+		number         float64
+		value          float64
+		delta          float64
+		wantInDelta    chainResult
+		wantNotInDelta chainResult
 	}{
 		{
-			name:         "larger value in delta range",
-			number:       1234.5,
-			value:        1234.7,
-			delta:        0.3,
-			isInDelta:    true,
-			isNotInDelta: false,
+			name:           "larger value in delta range",
+			number:         1234.5,
+			value:          1234.7,
+			delta:          0.3,
+			wantInDelta:    success,
+			wantNotInDelta: failure,
 		},
 		{
-			name:         "smaller value in delta range",
-			number:       1234.5,
-			value:        1234.3,
-			delta:        0.3,
-			isInDelta:    true,
-			isNotInDelta: false,
+			name:           "smaller value in delta range",
+			number:         1234.5,
+			value:          1234.3,
+			delta:          0.3,
+			wantInDelta:    success,
+			wantNotInDelta: failure,
 		},
 		{
-			name:         "larger value not in delta range",
-			number:       1234.5,
-			value:        1234.7,
-			delta:        0.1,
-			isInDelta:    false,
-			isNotInDelta: true,
+			name:           "larger value not in delta range",
+			number:         1234.5,
+			value:          1234.7,
+			delta:          0.1,
+			wantInDelta:    failure,
+			wantNotInDelta: success,
 		},
 		{
-			name:         "smaller value not in delta range",
-			number:       1234.5,
-			value:        1234.3,
-			delta:        0.1,
-			isInDelta:    false,
-			isNotInDelta: true,
+			name:           "smaller value not in delta range",
+			number:         1234.5,
+			value:          1234.3,
+			delta:          0.1,
+			wantInDelta:    failure,
+			wantNotInDelta: success,
 		},
 		{
-			name:         "number is NaN",
-			number:       math.NaN(),
-			value:        1234.0,
-			delta:        0.1,
-			isInDelta:    false,
-			isNotInDelta: false,
+			name:           "number is NaN",
+			number:         math.NaN(),
+			value:          1234.0,
+			delta:          0.1,
+			wantInDelta:    failure,
+			wantNotInDelta: failure,
 		},
 		{
-			name:         "value is NaN",
-			number:       1234.5,
-			value:        math.NaN(),
-			delta:        0.1,
-			isInDelta:    false,
-			isNotInDelta: false,
+			name:           "value is NaN",
+			number:         1234.5,
+			value:          math.NaN(),
+			delta:          0.1,
+			wantInDelta:    failure,
+			wantNotInDelta: failure,
 		},
 		{
-			name:         "delta is NaN",
-			number:       1234.5,
-			value:        1234.0,
-			delta:        math.NaN(),
-			isInDelta:    false,
-			isNotInDelta: false,
+			name:           "delta is NaN",
+			number:         1234.5,
+			value:          1234.0,
+			delta:          math.NaN(),
+			wantInDelta:    failure,
+			wantNotInDelta: failure,
 		},
 	}
 
@@ -315,25 +302,11 @@ func TestNumber_InDelta(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			reporter := newMockReporter(t)
 
-			if tc.isInDelta {
-				NewNumber(reporter, tc.number).
-					InDelta(tc.value, tc.delta).
-					chain.assertNotFailed(t)
-			} else {
-				NewNumber(reporter, tc.number).
-					InDelta(tc.value, tc.delta).
-					chain.assertFailed(t)
-			}
-
-			if tc.isNotInDelta {
-				NewNumber(reporter, tc.number).
-					NotInDelta(tc.value, tc.delta).
-					chain.assertNotFailed(t)
-			} else {
-				NewNumber(reporter, tc.number).
-					NotInDelta(tc.value, tc.delta).
-					chain.assertFailed(t)
-			}
+			NewNumber(reporter, tc.number).InDelta(tc.value, tc.delta).
+				chain.assert(t, tc.wantInDelta)
+
+			NewNumber(reporter, tc.number).NotInDelta(tc.value, tc.delta).
+				chain.assert(t, tc.wantNotInDelta)
 		})
 	}
 }
@@ -341,60 +314,60 @@ func TestNumber_InDelta(t *testing.T) {
 func TestNumber_InRange(t *testing.T) {
 	t.Run("basic", func(t *testing.T) {
 		cases := []struct {
-			name         string
-			number       float64
-			min          interface{}
-			max          interface{}
-			isInRange    bool
-			isNotInRange bool
+			name           string
+			number         float64
+			min            interface{}
+			max            interface{}
+			wantInRange    chainResult
+			wantNotInRange chainResult
 		}{
 			{
-				name:         "range includes only number",
-				number:       1234,
-				min:          1234,
-				max:          1234,
-				isInRange:    true,
-				isNotInRange: false,
+				name:           "range includes only number",
+				number:         1234,
+				min:            1234,
+				max:            1234,
+				wantInRange:    success,
+				wantNotInRange: failure,
 			},
 			{
-				name:         "range includes number and below",
-				number:       1234,
-				min:          1234 - 1,
-				max:          1234,
-				isInRange:    true,
-				isNotInRange: false,
+				name:           "range includes number and below",
+				number:         1234,
+				min:            1234 - 1,
+				max:            1234,
+				wantInRange:    success,
+				wantNotInRange: failure,
 			},
 			{
-				name:         "range includes number and above",
-				number:       1234,
-				min:          1234,
-				max:          1234 + 1,
-				isInRange:    true,
-				isNotInRange: false,
+				name:           "range includes number and above",
+				number:         1234,
+				min:            1234,
+				max:            1234 + 1,
+				wantInRange:    success,
+				wantNotInRange: failure,
 			},
 			{
-				name:         "range is above number",
-				number:       1234,
-				min:          1234 + 1,
-				max:          1234 + 2,
-				isInRange:    false,
-				isNotInRange: true,
+				name:           "range is above number",
+				number:         1234,
+				min:            1234 + 1,
+				max:            1234 + 2,
+				wantInRange:    failure,
+				wantNotInRange: success,
 			},
 			{
-				name:         "range is below number",
-				number:       1234,
-				min:          1234 - 2,
-				max:          1234 - 1,
-				isInRange:    false,
-				isNotInRange: true,
+				name:           "range is below number",
+				number:         1234,
+				min:            1234 - 2,
+				max:            1234 - 1,
+				wantInRange:    failure,
+				wantNotInRange: success,
 			},
 			{
-				name:         "range min is larger than max",
-				number:       1234,
-				min:          1234 + 1,
-				max:          1234 - 1,
-				isInRange:    false,
-				isNotInRange: true,
+				name:           "range min is larger than max",
+				number:         1234,
+				min:            1234 + 1,
+				max:            1234 - 1,
+				wantInRange:    failure,
+				wantNotInRange: success,
 			},
 		}
 
@@ -402,70 +375,44 @@ func TestNumber_InRange(t *testing.T) {
 			t.Run(tc.name, func(t *testing.T) {
 				reporter := newMockReporter(t)
 
-				if tc.isInRange {
-					NewNumber(reporter, tc.number).
-						InRange(tc.min, tc.max).
-						chain.assertNotFailed(t)
-				} else {
-					NewNumber(reporter, tc.number).
-						InRange(tc.min, tc.max).
-						chain.assertFailed(t)
-				}
-
-				if tc.isNotInRange {
-					NewNumber(reporter, tc.number).
-						NotInRange(tc.min, tc.max).
-						chain.assertNotFailed(t)
-				} else {
-					NewNumber(reporter, tc.number).
-						NotInRange(tc.min, tc.max).
-						chain.assertFailed(t)
-				}
+				NewNumber(reporter, tc.number).InRange(tc.min, tc.max).
+					chain.assert(t, tc.wantInRange)
+
+				NewNumber(reporter, tc.number).NotInRange(tc.min, tc.max).
+					chain.assert(t, tc.wantNotInRange)
 			})
 		}
 	})
 
 	t.Run("canonization", func(t *testing.T) {
 		cases := []struct {
-			number    float64
-			min       interface{}
-			max       interface{}
-			isInRange bool
+			number      float64
+			min         interface{}
+			max         interface{}
+			wantInRange chainResult
 		}{
 			{
-				number:    1234,
-				min:       int64(1233),
-				max:       float32(1235),
-				isInRange: true,
+				number:      1234,
+				min:         int64(1233),
+				max:         float32(1235),
+				wantInRange: success,
 			},
 			{
-				number:    1234,
-				min:       1235,
-				max:       1236,
-				isInRange: false,
+				number:      1234,
+				min:         1235,
+				max:         1236,
+				wantInRange: failure,
 			},
 		}
 
 		reporter := newMockReporter(t)
 
 		for _, tc := range cases {
-			if tc.isInRange {
-				NewNumber(reporter, tc.number).
-					InRange(tc.min, tc.max).
-					chain.assertNotFailed(t)
-
-				NewNumber(reporter, tc.number).
-					NotInRange(tc.min, tc.max).
-					chain.assertFailed(t)
-			} else {
-				NewNumber(reporter, tc.number).
-					NotInRange(tc.min, tc.max).
-					chain.assertNotFailed(t)
-
-				NewNumber(reporter, tc.number).
-					InRange(tc.min, tc.max).
-					chain.assertFailed(t)
-			}
+			NewNumber(reporter, tc.number).InRange(tc.min, tc.max).
+				chain.assert(t, tc.wantInRange)
+
+			NewNumber(reporter, tc.number).NotInRange(tc.min, tc.max).
+				chain.assert(t, !tc.wantInRange)
 		}
 	})
 
@@ -473,62 +420,62 @@ func TestNumber_InRange(t *testing.T) {
 		reporter := newMockReporter(t)
 
 		NewNumber(reporter, 1234).InRange(int64(1233), "NOT NUMBER").
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 1234).NotInRange(int64(1233), "NOT NUMBER").
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 1234).InRange("NOT NUMBER", float32(1235)).
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 1234).NotInRange("NOT NUMBER", float32(1235)).
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 	})
 }
 
 func TestNumber_InList(t *testing.T) {
 	t.Run("basic", func(t *testing.T) {
 		cases := []struct {
-			name        string
-			number      float64
-			list        []interface{}
-			isInList    bool
-			isNotInList bool
+			name          string
+			number        float64
+			list          []interface{}
+			wantInList    chainResult
+			wantNotInList chainResult
 		}{
 			{
-				name:        "no list",
-				number:      1234,
-				list:        nil,
-				isInList:    false,
-				isNotInList: false,
+				name:          "no list",
+				number:        1234,
+				list:          nil,
+				wantInList:    failure,
+				wantNotInList: failure,
 			},
 			{
-				name:        "empty list",
-				number:      1234,
-				list:        []interface{}{},
-				isInList:    false,
-				isNotInList: false,
+				name:          "empty list",
+				number:        1234,
+				list:          []interface{}{},
+				wantInList:    failure,
+				wantNotInList: failure,
 			},
 			{
-				name:        "in integer list",
-				number:      1234,
-				list:        []interface{}{1234, 4567},
-				isInList:    true,
-				isNotInList: false,
+				name:          "in integer list",
+				number:        1234,
+				list:          []interface{}{1234, 4567},
+				wantInList:    success,
+				wantNotInList: failure,
 			},
 			{
-				name:        "in float list",
-				number:      1234,
-				list:        []interface{}{1234.00, 4567.00},
-				isInList:    true,
-				isNotInList: false,
+				name:          "in float list",
+				number:        1234,
+				list:          []interface{}{1234.00, 4567.00},
+				wantInList:    success,
+				wantNotInList: failure,
 			},
 			{
-				name:        "not in float list",
-				number:      1234,
-				list:        []interface{}{4567.00, 1234.01},
-				isInList:    false,
-				isNotInList: true,
+				name:          "not in float list",
+				number:        1234,
+				list:          []interface{}{4567.00, 1234.01},
+				wantInList:    failure,
+				wantNotInList: success,
 			},
 		}
 
@@ -536,72 +483,46 @@ func TestNumber_InList(t *testing.T) {
 			t.Run(tc.name, func(t *testing.T) {
 				reporter := newMockReporter(t)
 
-				if tc.isInList {
-					NewNumber(reporter, tc.number).
-						InList(tc.list...).
-						chain.assertNotFailed(t)
-				} else {
-					NewNumber(reporter, tc.number).
-						InList(tc.list...).
-						chain.assertFailed(t)
-				}
-
-				if tc.isNotInList {
-					NewNumber(reporter, tc.number).
-						NotInList(tc.list...).
-						chain.assertNotFailed(t)
-				} else {
-					NewNumber(reporter, tc.number).
-						NotInList(tc.list...).
-						chain.assertFailed(t)
-				}
+				NewNumber(reporter, tc.number).InList(tc.list...).
+					chain.assert(t, tc.wantInList)
+
+				NewNumber(reporter, tc.number).NotInList(tc.list...).
+					chain.assert(t, tc.wantNotInList)
 			})
 		}
 	})
 
 	t.Run("canonization", func(t *testing.T) {
 		cases := []struct {
-			number   float64
-			list     []interface{}
-			isInList bool
+			number     float64
+			list       []interface{}
+			wantInList chainResult
 		}{
 			{
-				number:   111,
-				list:     []interface{}{int64(111), float32(222)},
-				isInList: true,
+				number:     111,
+				list:       []interface{}{int64(111), float32(222)},
+				wantInList: success,
 			},
 			{
-				number:   111,
-				list:     []interface{}{float32(111), int64(222)},
-				isInList: true,
+				number:     111,
+				list:       []interface{}{float32(111), int64(222)},
+				wantInList: success,
 			},
 			{
-				number:   111,
-				list:     []interface{}{222, 333},
-				isInList: false,
+				number:     111,
+				list:       []interface{}{222, 333},
+				wantInList: failure,
 			},
 		}
 
 		reporter := newMockReporter(t)
 
 		for _, tc := range cases {
-			if tc.isInList {
-				NewNumber(reporter, tc.number).
-					InList(tc.list...).
-					chain.assertNotFailed(t)
-
-				NewNumber(reporter, tc.number).
-					NotInList(tc.list...).
-					chain.assertFailed(t)
-			} else {
-				NewNumber(reporter, tc.number).
-					NotInList(tc.list...).
-					chain.assertNotFailed(t)
-
-				NewNumber(reporter, tc.number).
-					InList(tc.list...).
-					chain.assertFailed(t)
-			}
+			NewNumber(reporter, tc.number).InList(tc.list...).
+				chain.assert(t, tc.wantInList)
+
+			NewNumber(reporter, tc.number).NotInList(tc.list...).
+				chain.assert(t, !tc.wantInList)
 		}
 	})
 
@@ -609,16 +530,16 @@ func TestNumber_InList(t *testing.T) {
 		reporter := newMockReporter(t)
 
 		NewNumber(reporter, 111).InList(222, "NOT NUMBER").
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 111).NotInList(222, "NOT NUMBER").
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 111).InList(111, "NOT NUMBER").
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 111).NotInList(111, "NOT NUMBER").
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 	})
 }
 
@@ -628,29 +549,29 @@ func TestNumber_IsGreater(t *testing.T) {
 			name   string
 			number float64
 			value  interface{}
-			isGt   bool
-			isGe   bool
+			wantGt chainResult
+			wantGe chainResult
 		}{
 			{
 				name:   "number is lesser",
 				number: 1234,
 				value:  1234 + 1,
-				isGt:   false,
-				isGe:   false,
+				wantGt: failure,
+				wantGe: failure,
 			},
 			{
 				name:   "number is equal",
 				number: 1234,
 				value:  1234,
-				isGt:   false,
-				isGe:   true,
+				wantGt: failure,
+				wantGe: success,
 			},
 			{
 				name:   "number is greater",
 				number: 1234,
 				value:  1234 - 1,
-				isGt:   true,
-				isGe:   true,
+				wantGt: success,
+				wantGe: success,
 			},
 		}
 
@@ -658,25 +579,11 @@ func TestNumber_IsGreater(t *testing.T) {
 			t.Run(tc.name, func(t *testing.T) {
 				reporter := newMockReporter(t)
 
-				if tc.isGt {
-					NewNumber(reporter, tc.number).
-						Gt(tc.value).
-						chain.assertNotFailed(t)
-				} else {
-					NewNumber(reporter, tc.number).
-						Gt(tc.value).
-						chain.assertFailed(t)
-				}
-
-				if tc.isGe {
-					NewNumber(reporter, tc.number).
-						Ge(tc.value).
-						chain.assertNotFailed(t)
-				} else {
-					NewNumber(reporter, tc.number).
-						Ge(tc.value).
-						chain.assertFailed(t)
-				}
+				NewNumber(reporter, tc.number).Gt(tc.value).
+					chain.assert(t, tc.wantGt)
+
+				NewNumber(reporter, tc.number).Ge(tc.value).
+					chain.assert(t, tc.wantGe)
 			})
 		}
 	})
@@ -685,26 +592,26 @@ func TestNumber_IsGreater(t *testing.T) {
 		reporter := newMockReporter(t)
 
 		NewNumber(reporter, 1234).Gt(int64(1233)).
-			chain.assertNotFailed(t)
+			chain.assert(t, success)
 
 		NewNumber(reporter, 1234).Gt(float32(1233)).
-			chain.assertNotFailed(t)
+			chain.assert(t, success)
 
 		NewNumber(reporter, 1234).Ge(int64(1233)).
-			chain.assertNotFailed(t)
+			chain.assert(t, success)
 
 		NewNumber(reporter, 1234).Ge(float32(1233)).
-			chain.assertNotFailed(t)
+			chain.assert(t, success)
 	})
 
 	t.Run("invalid argument", func(t *testing.T) {
 		reporter := newMockReporter(t)
 
 		NewNumber(reporter, 1234).Gt("NOT NUMBER").
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 1234).Ge("NOT NUMBER").
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 	})
 }
 
@@ -714,29 +621,29 @@ func TestNumber_IsLesser(t *testing.T) {
 			name   string
 			number float64
 			value  interface{}
-			isLt   bool
-			isLe   bool
+			wantLt chainResult
+			wantLe chainResult
 		}{
 			{
 				name:   "number is lesser",
 				number: 1234,
 				value:  1234 + 1,
-				isLt:   true,
-				isLe:   true,
+				wantLt: success,
+				wantLe: success,
 			},
 			{
 				name:   "number is equal",
 				number: 1234,
 				value:  1234,
-				isLt:   false,
-				isLe:   true,
+				wantLt: failure,
+				wantLe: success,
 			},
 			{
 				name:   "number is greater",
 				number: 1234,
 				value:  1234 - 1,
-				isLt:   false,
-				isLe:   false,
+				wantLt: failure,
+				wantLe: failure,
 			},
 		}
 
@@ -744,25 +651,11 @@ func TestNumber_IsLesser(t *testing.T) {
 			t.Run(tc.name, func(t *testing.T) {
 				reporter := newMockReporter(t)
 
-				if tc.isLt {
-					NewNumber(reporter, tc.number).
-						Lt(tc.value).
-						chain.assertNotFailed(t)
-				} else {
-					NewNumber(reporter, tc.number).
-						Lt(tc.value).
-						chain.assertFailed(t)
-				}
-
-				if tc.isLe {
-					NewNumber(reporter, tc.number).
-						Le(tc.value).
-						chain.assertNotFailed(t)
-				} else {
-					NewNumber(reporter, tc.number).
-						Le(tc.value).
-						chain.assertFailed(t)
-				}
+				NewNumber(reporter, tc.number).Lt(tc.value).
+					chain.assert(t, tc.wantLt)
+
+				NewNumber(reporter, tc.number).Le(tc.value).
+					chain.assert(t, tc.wantLe)
 			})
 		}
 	})
@@ -771,135 +664,135 @@ func TestNumber_IsLesser(t *testing.T) {
 		reporter := newMockReporter(t)
 
 		NewNumber(reporter, 1234).Lt(int64(1235)).
-			chain.assertNotFailed(t)
+			chain.assert(t, success)
 
 		NewNumber(reporter, 1234).Lt(float32(1235)).
-			chain.assertNotFailed(t)
+			chain.assert(t, success)
 
 		NewNumber(reporter, 1234).Le(int64(1235)).
-			chain.assertNotFailed(t)
+			chain.assert(t, success)
 
 		NewNumber(reporter, 1234).Le(float32(1235)).
-			chain.assertNotFailed(t)
+			chain.assert(t, success)
 	})
 
 	t.Run("invalid argument", func(t *testing.T) {
 		reporter := newMockReporter(t)
 
 		NewNumber(reporter, 1234).Lt("NOT NUMBER").
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 1234).Le("NOT NUMBER").
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 	})
 }
 
 func TestNumber_IsInt(t *testing.T) {
 	t.Run("values", func(t *testing.T) {
 		cases := []struct {
-			name    string
-			value   float64
-			isInt16 bool
-			isInt32 bool
-			isInt   bool
+			name      string
+			value     float64
+			wantInt16 chainResult
+			wantInt32 chainResult
+			wantInt   chainResult
 		}{
 			{
-				name:    "0",
-				value:   0,
-				isInt16: true,
-				isInt32: true,
-				isInt:   true,
+				name:      "0",
+				value:     0,
+				wantInt16: success,
+				wantInt32: success,
+				wantInt:   success,
 			},
 			{
-				name:    "1",
-				value:   1,
-				isInt16: true,
-				isInt32: true,
-				isInt:   true,
+				name:      "1",
+				value:     1,
+				wantInt16: success,
+				wantInt32: success,
+				wantInt:   success,
 			},
 			{
-				name:    "0.5",
-				value:   0.5,
-				isInt16: false,
-				isInt32: false,
-				isInt:   false,
+				name:      "0.5",
+				value:     0.5,
+				wantInt16: failure,
+				wantInt32: failure,
+				wantInt:   failure,
 			},
 			{
-				name:    "NaN",
-				value:   math.NaN(),
-				isInt16: false,
-				isInt32: false,
-				isInt:   false,
+				name:      "NaN",
+				value:     math.NaN(),
+				wantInt16: failure,
+				wantInt32: failure,
+				wantInt:   failure,
 			},
 			{
-				name:    "-Inf",
-				value:   math.Inf(-1),
-				isInt16: false,
-				isInt32: false,
-				isInt:   false,
+				name:      "-Inf",
+				value:     math.Inf(-1),
+				wantInt16: failure,
+				wantInt32: failure,
+				wantInt:   failure,
 			},
 			{
-				name:    "+Inf",
-				value:   math.Inf(+1),
-				isInt16: false,
-				isInt32: false,
-				isInt:   false,
+				name:      "+Inf",
+				value:     math.Inf(+1),
+				wantInt16: failure,
+				wantInt32: failure,
+				wantInt:   failure,
 			},
 			{
-				name:    "MinInt16-1",
-				value:   math.MinInt16 - 1,
-				isInt16: false,
-				isInt32: true,
-				isInt:   true,
+				name:      "MinInt16-1",
+				value:     math.MinInt16 - 1,
+				wantInt16: failure,
+				wantInt32: success,
+				wantInt:   success,
 			},
 			{
-				name:    "MinInt16",
-				value:   math.MinInt16,
-				isInt16: true,
-				isInt32: true,
-				isInt:   true,
+				name:      "MinInt16",
+				value:     math.MinInt16,
+				wantInt16: success,
+				wantInt32: success,
+				wantInt:   success,
 			},
 			{
-				name:    "MaxInt16",
-				value:   math.MaxInt16,
-				isInt16: true,
-				isInt32: true,
-				isInt:   true,
+				name:      "MaxInt16",
+				value:     math.MaxInt16,
+				wantInt16: success,
+				wantInt32: success,
+				wantInt:   success,
 			},
 			{
-				name:    "MaxInt16+1",
-				value:   math.MaxInt16 + 1,
-				isInt16: false,
-				isInt32: true,
-				isInt:   true,
+				name:      "MaxInt16+1",
+				value:     math.MaxInt16 + 1,
+				wantInt16: failure,
+				wantInt32: success,
+				wantInt:   success,
 			},
 			{
-				name:    "MinInt32-1",
-				value:   math.MinInt32 - 1,
-				isInt16: false,
-				isInt32: false,
-				isInt:   true,
+				name:      "MinInt32-1",
+				value:     math.MinInt32 - 1,
+				wantInt16: failure,
+				wantInt32: failure,
+				wantInt:   success,
 			},
 			{
-				name:    "MinInt32",
-				value:   math.MinInt32,
-				isInt16: false,
-				isInt32: true,
-				isInt:   true,
+				name:      "MinInt32",
+				value:     math.MinInt32,
+				wantInt16: failure,
+				wantInt32: success,
+				wantInt:   success,
 			},
 			{
-				name:    "MaxInt32",
-				value:   math.MaxInt32,
-				isInt16: false,
-				isInt32: true,
-				isInt:   true,
+				name:      "MaxInt32",
+				value:     math.MaxInt32,
+				wantInt16: failure,
+				wantInt32: success,
+				wantInt:   success,
 			},
 			{
-				name:    "MaxInt32+1",
-				value:   math.MaxInt32 + 1,
-				isInt16: false,
-				isInt32: false,
-				isInt:   true,
+				name:      "MaxInt32+1",
+				value:     math.MaxInt32 + 1,
+				wantInt16: failure,
+				wantInt32: failure,
+				wantInt:   success,
 			},
 		}
 
@@ -907,29 +800,20 @@ func TestNumber_IsInt(t *testing.T) {
 			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)
-				}
+				NewNumber(reporter, tc.value).IsInt().
+					chain.assert(t, tc.wantInt)
+				NewNumber(reporter, tc.value).NotInt().
+					chain.assert(t, !tc.wantInt)
+
+				NewNumber(reporter, tc.value).IsInt(32).
+					chain.assert(t, tc.wantInt32)
+				NewNumber(reporter, tc.value).NotInt(32).
+					chain.assert(t, !tc.wantInt32)
+
+				NewNumber(reporter, tc.value).IsInt(16).
+					chain.assert(t, tc.wantInt16)
+				NewNumber(reporter, tc.value).NotInt(16).
+					chain.assert(t, !tc.wantInt16)
 			})
 		}
 	})
@@ -938,110 +822,107 @@ func TestNumber_IsInt(t *testing.T) {
 		reporter := newMockReporter(t)
 
 		NewNumber(reporter, 1234).IsInt(16, 32).
-			chain.assertFailed(t)
-
+			chain.assert(t, failure)
 		NewNumber(reporter, 1234).NotInt(16, 32).
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 1234).IsInt(0).
-			chain.assertFailed(t)
-
+			chain.assert(t, failure)
 		NewNumber(reporter, 1234).NotInt(0).
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 1234).IsInt(-16).
-			chain.assertFailed(t)
-
+			chain.assert(t, failure)
 		NewNumber(reporter, 1234).NotInt(-16).
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 	})
 }
 
 func TestNumber_IsUint(t *testing.T) {
 	t.Run("values", func(t *testing.T) {
 		cases := []struct {
-			name     string
-			value    float64
-			isUint16 bool
-			isUint32 bool
-			isUint   bool
+			name       string
+			value      float64
+			wantUint16 chainResult
+			wantUint32 chainResult
+			wantUint   chainResult
 		}{
 			{
-				name:     "0",
-				value:    0,
-				isUint16: true,
-				isUint32: true,
-				isUint:   true,
+				name:       "0",
+				value:      0,
+				wantUint16: success,
+				wantUint32: success,
+				wantUint:   success,
 			},
 			{
-				name:     "1",
-				value:    1,
-				isUint16: true,
-				isUint32: true,
-				isUint:   true,
+				name:       "1",
+				value:      1,
+				wantUint16: success,
+				wantUint32: success,
+				wantUint:   success,
 			},
 			{
-				name:     "-1",
-				value:    -1,
-				isUint16: false,
-				isUint32: false,
-				isUint:   false,
+				name:       "-1",
+				value:      -1,
+				wantUint16: failure,
+				wantUint32: failure,
+				wantUint:   failure,
 			},
 			{
-				name:     "0.5",
-				value:    0.5,
-				isUint16: false,
-				isUint32: false,
-				isUint:   false,
+				name:       "0.5",
+				value:      0.5,
+				wantUint16: failure,
+				wantUint32: failure,
+				wantUint:   failure,
 			},
 			{
-				name:     "NaN",
-				value:    math.NaN(),
-				isUint16: false,
-				isUint32: false,
-				isUint:   false,
+				name:       "NaN",
+				value:      math.NaN(),
+				wantUint16: failure,
+				wantUint32: failure,
+				wantUint:   failure,
 			},
 			{
-				name:     "-Inf",
-				value:    math.Inf(-1),
-				isUint16: false,
-				isUint32: false,
-				isUint:   false,
+				name:       "-Inf",
+				value:      math.Inf(-1),
+				wantUint16: failure,
+				wantUint32: failure,
+				wantUint:   failure,
 			},
 			{
-				name:     "+Inf",
-				value:    math.Inf(+1),
-				isUint16: false,
-				isUint32: false,
-				isUint:   false,
+				name:       "+Inf",
+				value:      math.Inf(+1),
+				wantUint16: failure,
+				wantUint32: failure,
+				wantUint:   failure,
 			},
 			{
-				name:     "MaxUint16",
-				value:    math.MaxUint16,
-				isUint16: true,
-				isUint32: true,
-				isUint:   true,
+				name:       "MaxUint16",
+				value:      math.MaxUint16,
+				wantUint16: success,
+				wantUint32: success,
+				wantUint:   success,
 			},
 			{
-				name:     "MaxUint16+1",
-				value:    math.MaxUint16 + 1,
-				isUint16: false,
-				isUint32: true,
-				isUint:   true,
+				name:       "MaxUint16+1",
+				value:      math.MaxUint16 + 1,
+				wantUint16: failure,
+				wantUint32: success,
+				wantUint:   success,
 			},
 			{
-				name:     "MaxUint32",
-				value:    math.MaxUint32,
-				isUint16: false,
-				isUint32: true,
-				isUint:   true,
+				name:       "MaxUint32",
+				value:      math.MaxUint32,
+				wantUint16: failure,
+				wantUint32: success,
+				wantUint:   success,
 			},
 			{
-				name:     "MaxUint32+1",
-				value:    math.MaxUint32 + 1,
-				isUint16: false,
-				isUint32: false,
-				isUint:   true,
+				name:       "MaxUint32+1",
+				value:      math.MaxUint32 + 1,
+				wantUint16: failure,
+				wantUint32: failure,
+				wantUint:   success,
 			},
 		}
 
@@ -1049,29 +930,20 @@ func TestNumber_IsUint(t *testing.T) {
 			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)
-				}
+				NewNumber(reporter, tc.value).IsUint().
+					chain.assert(t, tc.wantUint)
+				NewNumber(reporter, tc.value).NotUint().
+					chain.assert(t, !tc.wantUint)
+
+				NewNumber(reporter, tc.value).IsUint(32).
+					chain.assert(t, tc.wantUint32)
+				NewNumber(reporter, tc.value).NotUint(32).
+					chain.assert(t, !tc.wantUint32)
+
+				NewNumber(reporter, tc.value).IsUint(16).
+					chain.assert(t, tc.wantUint16)
+				NewNumber(reporter, tc.value).NotUint(16).
+					chain.assert(t, !tc.wantUint16)
 			})
 		}
 	})
@@ -1080,65 +952,62 @@ func TestNumber_IsUint(t *testing.T) {
 		reporter := newMockReporter(t)
 
 		NewNumber(reporter, 1234).IsUint(16, 32).
-			chain.assertFailed(t)
-
+			chain.assert(t, failure)
 		NewNumber(reporter, 1234).NotUint(16, 32).
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 1234).IsUint(0).
-			chain.assertFailed(t)
-
+			chain.assert(t, failure)
 		NewNumber(reporter, 1234).NotUint(0).
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 
 		NewNumber(reporter, 1234).IsUint(-16).
-			chain.assertFailed(t)
-
+			chain.assert(t, failure)
 		NewNumber(reporter, 1234).NotUint(-16).
-			chain.assertFailed(t)
+			chain.assert(t, failure)
 	})
 }
 
 func TestNumber_IsFinite(t *testing.T) {
 	cases := []struct {
-		name     string
-		value    float64
-		isFinite bool
+		name       string
+		value      float64
+		wantFinite chainResult
 	}{
 		{
-			name:     "0",
-			value:    0,
-			isFinite: true,
+			name:       "0",
+			value:      0,
+			wantFinite: success,
 		},
 		{
-			name:     "1",
-			value:    1,
-			isFinite: true,
+			name:       "1",
+			value:      1,
+			wantFinite: success,
 		},
 		{
-			name:     "-1",
-			value:    -1,
-			isFinite: true,
+			name:       "-1",
+			value:      -1,
+			wantFinite: success,
 		},
 		{
-			name:     "0.5",
-			value:    0.5,
-			isFinite: true,
+			name:       "0.5",
+			value:      0.5,
+			wantFinite: success,
 		},
 		{
-			name:     "NaN",
-			value:    math.NaN(),
-			isFinite: false,
+			name:       "NaN",
+			value:      math.NaN(),
+			wantFinite: failure,
 		},
 		{
-			name:     "-Inf",
-			value:    math.Inf(-1),
-			isFinite: false,
+			name:       "-Inf",
+			value:      math.Inf(-1),
+			wantFinite: failure,
 		},
 		{
-			name:     "+Inf",
-			value:    math.Inf(+1),
-			isFinite: false,
+			name:       "+Inf",
+			value:      math.Inf(+1),
+			wantFinite: failure,
 		},
 	}
 
@@ -1146,13 +1015,11 @@ func TestNumber_IsFinite(t *testing.T) {
 		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)
-			}
+			NewNumber(reporter, tc.value).IsFinite().
+				chain.assert(t, tc.wantFinite)
+
+			NewNumber(reporter, tc.value).NotFinite().
+				chain.assert(t, !tc.wantFinite)
 		})
 	}
 }
diff --git a/object_test.go b/object_test.go
index 0fed45f..a18a89f 100644
--- a/object_test.go
+++ b/object_test.go
@@ -65,7 +65,7 @@ func TestObject_FailedChain(t *testing.T) {
 	}
 
 	t.Run("failed chain", func(t *testing.T) {
-		chain := newFailedChain(t)
+		chain := newMockChain(t, flagFailed)
 		value := newObject(chain, map[string]interface{}{})
 
 		check(value)
@@ -79,7 +79,7 @@ func TestObject_FailedChain(t *testing.T) {
 	})
 
 	t.Run("failed chain, nil value", func(t *testing.T) {
-		chain := newFailedChain(t)
+		chain := newMockChain(t, flagFailed)
 		value := newObject(chain, nil)
 
 		check(value)
diff --git a/request_test.go b/request_test.go
index ded6357..c07ed85 100644
--- a/request_test.go
+++ b/request_test.go
@@ -5,10 +5,12 @@ import (
 	"context"
 	"encoding/json"
 	"errors"
+	"io"
 	"io/ioutil"
 	"mime"
 	"mime/multipart"
 	"net/http"
+	neturl "net/url"
 	"os"
 	"path/filepath"
 	"strings"
@@ -22,10 +24,8 @@ import (
 
 func TestRequest_FailedChain(t *testing.T) {
 	reporter := newMockReporter(t)
-	chain := newChainWithDefaults("test", reporter)
 	config := newMockConfig(reporter)
-
-	chain.setFailed()
+	chain := newChainWithDefaults("test", reporter, flagFailed)
 
 	req := newRequest(chain, config, "GET", "")
 	req.chain.assertFailed(t)
@@ -1419,6 +1419,56 @@ func TestRequest_BodyMultipart(t *testing.T) {
 		eof, _ := reader.NextPart()
 		assert.Nil(t, eof)
 	})
+
+	t.Run("multipart writer error", func(t *testing.T) {
+		cases := []struct {
+			name     string
+			reqFn    func(*Request)
+			assertOk bool
+		}{
+			{
+				name: "with form",
+				reqFn: func(req *Request) {
+					req.WithForm(map[string]string{"foo": "bar"})
+				},
+				assertOk: false,
+			},
+			{
+				name: "with form field",
+				reqFn: func(req *Request) {
+					req.WithFormField("foo", "bar")
+				},
+				assertOk: false,
+			},
+			{
+				name: "with file",
+				reqFn: func(req *Request) {
+					req.WithFile("foo", "bar", strings.NewReader("baz"))
+				},
+				assertOk: false,
+			},
+		}
+
+		for _, tc := range cases {
+			t.Run(tc.name, func(t *testing.T) {
+				req := NewRequestC(config, "POST", "url")
+				req.multipartFn = func(w io.Writer) *multipart.Writer {
+					return multipart.NewWriter(&mockWriter{
+						err: errors.New("mock writer error"),
+					})
+				}
+				req.WithMultipart()
+
+				tc.reqFn(req)
+
+				if tc.assertOk {
+					req.chain.assertNotFailed(t)
+				} else {
+					req.chain.assertFailed(t)
+				}
+			})
+		}
+	})
 }
 
 func TestRequest_BodyJSON(t *testing.T) {
@@ -1580,6 +1630,73 @@ func TestRequest_ContentType(t *testing.T) {
 	})
 }
 
+func TestRequest_Websocket(t *testing.T) {
+	t.Run("ws successful", func(t *testing.T) {
+		scheme := ""
+		dialer := WebsocketDialerFunc(func(
+			url string, _ http.Header,
+		) (*websocket.Conn, *http.Response, error) {
+			u, _ := neturl.Parse(url)
+			scheme = u.Scheme
+			return &websocket.Conn{}, &http.Response{}, nil
+		})
+		config := Config{
+			Reporter:        newMockReporter(t),
+			WebsocketDialer: dialer,
+			BaseURL:         "http://example.com",
+		}
+		req := NewRequestC(config, "GET", "url").WithWebsocketUpgrade()
+		req.Expect().chain.assertNotFailed(t)
+		assert.Equal(t, "ws", scheme)
+	})
+	t.Run("wss successful", func(t *testing.T) {
+		scheme := ""
+		dialer := WebsocketDialerFunc(func(
+			url string, _ http.Header,
+		) (*websocket.Conn, *http.Response, error) {
+			u, _ := neturl.Parse(url)
+			scheme = u.Scheme
+			return &websocket.Conn{}, &http.Response{}, nil
+		})
+		config := Config{
+			Reporter:        newMockReporter(t),
+			WebsocketDialer: dialer,
+			BaseURL:         "https://example.com",
+		}
+		req := NewRequestC(config, "GET", "url").WithWebsocketUpgrade()
+		req.Expect().chain.assertNotFailed(t)
+		assert.Equal(t, "wss", scheme)
+	})
+	t.Run("bad handshake", func(t *testing.T) {
+		dialer := WebsocketDialerFunc(func(
+			_ string, _ http.Header,
+		) (*websocket.Conn, *http.Response, error) {
+			return &websocket.Conn{}, &http.Response{}, websocket.ErrBadHandshake
+		})
+		config := Config{
+			Reporter:        newMockReporter(t),
+			WebsocketDialer: dialer,
+		}
+		req := NewRequestC(config, "GET", "url").WithWebsocketUpgrade()
+		req.Expect().chain.assertNotFailed(t)
+	})
+	t.Run("request body not allowed", func(t *testing.T) {
+		dialer := WebsocketDialerFunc(func(
+			_ string, _ http.Header,
+		) (*websocket.Conn, *http.Response, error) {
+			return &websocket.Conn{}, &http.Response{}, nil
+		})
+		config := Config{
+			Reporter:        newMockReporter(t),
+			WebsocketDialer: dialer,
+		}
+		req := NewRequestC(config, "GET", "url").
+			WithJSON("").
+			WithWebsocketUpgrade()
+		req.Expect().chain.assertFailed(t)
+	})
+}
+
 func TestRequest_RedirectsDontFollow(t *testing.T) {
 	t.Run("no body", func(t *testing.T) {
 		reporter := newMockReporter(t)
diff --git a/response_test.go b/response_test.go
index cb016e4..96251b5 100644
--- a/response_test.go
+++ b/response_test.go
@@ -43,10 +43,8 @@ func TestResponse_FailedChain(t *testing.T) {
 
 	t.Run("failed chain", func(t *testing.T) {
 		reporter := newMockReporter(t)
-		chain := newChainWithDefaults("test", reporter)
 		config := newMockConfig(reporter)
-
-		chain.setFailed()
+		chain := newChainWithDefaults("test", reporter, flagFailed)
 
 		resp := newResponse(responseOpts{
 			config:   config,
@@ -59,8 +57,8 @@ func TestResponse_FailedChain(t *testing.T) {
 
 	t.Run("nil value", func(t *testing.T) {
 		reporter := newMockReporter(t)
-		chain := newChainWithDefaults("test", reporter)
 		config := newMockConfig(reporter)
+		chain := newChainWithDefaults("test", reporter)
 
 		resp := newResponse(responseOpts{
 			config:   config,
@@ -73,10 +71,8 @@ func TestResponse_FailedChain(t *testing.T) {
 
 	t.Run("failed chain, nil value", func(t *testing.T) {
 		reporter := newMockReporter(t)
-		chain := newChainWithDefaults("test", reporter)
 		config := newMockConfig(reporter)
-
-		chain.setFailed()
+		chain := newChainWithDefaults("test", reporter, flagFailed)
 
 		resp := newResponse(responseOpts{
 			config:   config,
diff --git a/string_test.go b/string_test.go
index 71daed8..344d572 100644
--- a/string_test.go
+++ b/string_test.go
@@ -9,20 +9,19 @@ import (
 )
 
 func TestString_FailedChain(t *testing.T) {
-	chain := newMockChain(t)
-	chain.setFailed()
+	chain := newMockChain(t, flagFailed)
 
 	value := newString(chain, "")
-	value.chain.assertFailed(t)
+	value.chain.assert(t, failure)
 
-	value.Path("$").chain.assertFailed(t)
+	value.Path("$").chain.assert(t, failure)
 	value.Schema("")
 	value.Alias("foo")
 
 	var target interface{}
 	value.Decode(target)
 
-	value.Length().chain.assertFailed(t)
+	value.Length().chain.assert(t, failure)
 
 	value.IsEmpty()
 	value.NotEmpty()
@@ -49,14 +48,14 @@ func TestString_FailedChain(t *testing.T) {
 	value.IsASCII()
 	value.NotASCII()
 
-	value.Match("").chain.assertFailed(t)
+	value.Match("").chain.assert(t, failure)
 	value.NotMatch("")
 	assert.NotNil(t, value.MatchAll(""))
 	assert.Equal(t, 0, len(value.MatchAll("")))
 
-	value.AsBoolean().chain.assertFailed(t)
-	value.AsNumber().chain.assertFailed(t)
-	value.AsDateTime().chain.assertFailed(t)
+	value.AsBoolean().chain.assert(t, failure)
+	value.AsNumber().chain.assert(t, failure)
+	value.AsDateTime().chain.assert(t, failure)
 }
 
 func TestString_Constructors(t *testing.T) {
@@ -64,7 +63,7 @@ func TestString_Constructors(t *testing.T) {
 		reporter := newMockReporter(t)
 		value := NewString(reporter, "Hello")
 		value.IsEqual("Hello")
-		value.chain.assertNotFailed(t)
+		value.chain.assert(t, success)
 	})
 
 	t.Run("config", func(t *testing.T) {
@@ -73,7 +72,7 @@ func TestString_Constructors(t *testing.T) {
 			Reporter: reporter,
 		}, "Hello")
 		value.IsEqual("Hello")
-		value.chain.assertNotFailed(t)
+		value.chain.assert(t, success)
 	})
 
 	t.Run("chain", func(t *testing.T) {
@@ -93,7 +92,7 @@ func TestString_Decode(t *testing.T) {
 		var target interface{}
 		value.Decode(&target)
 
-		value.chain.assertNotFailed(t)
+		value.chain.assert(t, success)
 		assert.Equal(t, "foo", target)
 	})
 
@@ -105,7 +104,7 @@ func TestString_Decode(t *testing.T) {
 		var target string
 		value.Decode(&target)
 
-		value.chain.assertNotFailed(t)
+		value.chain.assert(t, success)
 		assert.Equal(t, "foo", target)
 	})
 
@@ -116,7 +115,7 @@ func TestString_Decode(t *testing.T) {
 
 		value.Decode(123)
 
-		value.chain.assertFailed(t)
+		value.chain.assert(t, failure)
 	})
 
 	t.Run("target is nil", func(t *testing.T) {
@@ -126,7 +125,7 @@ func TestString_Decode(t *testing.T) {
 
 		value.Decode(nil)
 
-		value.chain.assertFailed(t)
+		value.chain.assert(t, failure)
 	})
 }
 
@@ -152,42 +151,42 @@ func TestString_Getters(t *testing.T) {
 	value := NewString(reporter, "foo")
 
 	assert.Equal(t, "foo", value.Raw())
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	value.chain.assert(t, success)
+	value.chain.clear()
 
 	assert.Equal(t, "foo", value.Path("$").Raw())
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	value.chain.assert(t, success)
+	value.chain.clear()
 
 	value.Schema(`{"type": "string"}`)
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	value.chain.assert(t, success)
+	value.chain.clear()
 
 	value.Schema(`{"type": "object"}`)
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	value.chain.assert(t, failure)
+	value.chain.clear()
 
 	num := value.Length()
-	value.chain.assertNotFailed(t)
-	num.chain.assertNotFailed(t)
+	value.chain.assert(t, success)
+	num.chain.assert(t, success)
 	assert.Equal(t, 3.0, num.Raw())
 }
 
 func TestString_IsEmpty(t *testing.T) {
 	cases := []struct {
-		name    string
-		str     string
-		isEmpty bool
+		name      string
+		str       string
+		wantEmpty chainResult
 	}{
 		{
-			name:    "empty string",
-			str:     "",
-			isEmpty: true,
+			name:      "empty string",
+			str:       "",
+			wantEmpty: success,
 		},
 		{
-			name:    "non-empty string",
-			str:     "foo",
-			isEmpty: false,
+			name:      "non-empty string",
+			str:       "foo",
+			wantEmpty: failure,
 		},
 	}
 
@@ -195,396 +194,314 @@ func TestString_IsEmpty(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			reporter := newMockReporter(t)
 
-			if tc.isEmpty {
-				NewString(reporter, tc.str).IsEmpty().
-					chain.assertNotFailed(t)
-				NewString(reporter, tc.str).NotEmpty().
-					chain.assertFailed(t)
-			} else {
-				NewString(reporter, tc.str).IsEmpty().
-					chain.assertFailed(t)
-				NewString(reporter, tc.str).NotEmpty().
-					chain.assertNotFailed(t)
-			}
+			NewString(reporter, tc.str).IsEmpty().
+				chain.assert(t, tc.wantEmpty)
+
+			NewString(reporter, tc.str).NotEmpty().
+				chain.assert(t, !tc.wantEmpty)
 		})
 	}
 }
 
 func TestString_IsEqual(t *testing.T) {
 	cases := []struct {
-		name        string
-		str         string
-		value       string
-		isEqual     bool
-		isEqualFold bool
+		name          string
+		str           string
+		value         string
+		wantEqual     chainResult
+		wantEqualFold chainResult
 	}{
 		{
-			name:        "equivalent string",
-			str:         "foo",
-			value:       "foo",
-			isEqual:     true,
-			isEqualFold: true,
+			name:          "equivalent string",
+			str:           "foo",
+			value:         "foo",
+			wantEqual:     success,
+			wantEqualFold: success,
 		},
 		{
-			name:        "non-equivalent string",
-			str:         "foo",
-			value:       "bar",
-			isEqual:     false,
-			isEqualFold: false,
+			name:          "non-equivalent string",
+			str:           "foo",
+			value:         "bar",
+			wantEqual:     failure,
+			wantEqualFold: failure,
 		},
 		{
-			name:        "different case",
-			str:         "foo",
-			value:       "FOO",
-			isEqual:     false,
-			isEqualFold: true,
+			name:          "different case",
+			str:           "foo",
+			value:         "FOO",
+			wantEqual:     failure,
+			wantEqualFold: success,
 		},
 	}
 
 	for _, tc := range cases {
 		reporter := newMockReporter(t)
 
-		if tc.isEqual {
-			NewString(reporter, tc.str).IsEqual(tc.value).
-				chain.assertNotFailed(t)
-			NewString(reporter, tc.str).NotEqual(tc.value).
-				chain.assertFailed(t)
-		} else {
-			NewString(reporter, tc.str).IsEqual(tc.value).
-				chain.assertFailed(t)
-			NewString(reporter, tc.str).NotEqual(tc.value).
-				chain.assertNotFailed(t)
-		}
+		NewString(reporter, tc.str).IsEqual(tc.value).
+			chain.assert(t, tc.wantEqual)
+		NewString(reporter, tc.str).NotEqual(tc.value).
+			chain.assert(t, !tc.wantEqual)
 
-		if tc.isEqualFold {
-			NewString(reporter, tc.str).IsEqualFold(tc.value).
-				chain.assertNotFailed(t)
-			NewString(reporter, tc.str).NotEqualFold(tc.value).
-				chain.assertFailed(t)
-		} else {
-			NewString(reporter, tc.str).IsEqualFold(tc.value).
-				chain.assertFailed(t)
-			NewString(reporter, tc.str).NotEqualFold(tc.value).
-				chain.assertNotFailed(t)
-		}
+		NewString(reporter, tc.str).IsEqualFold(tc.value).
+			chain.assert(t, tc.wantEqualFold)
+		NewString(reporter, tc.str).NotEqualFold(tc.value).
+			chain.assert(t, !tc.wantEqualFold)
 	}
 }
 
 func TestString_InList(t *testing.T) {
 	t.Run("basic", func(t *testing.T) {
 		cases := []struct {
-			name       string
-			str        string
-			value      []string
-			inList     bool
-			inListFold bool
+			name           string
+			str            string
+			value          []string
+			wantInList     chainResult
+			wantInListFold chainResult
 		}{
 			{
-				name:       "in list",
-				str:        "foo",
-				value:      []string{"foo", "bar"},
-				inList:     true,
-				inListFold: true,
+				name:           "in list",
+				str:            "foo",
+				value:          []string{"foo", "bar"},
+				wantInList:     success,
+				wantInListFold: success,
 			},
 			{
-				name:       "not in list",
-				str:        "baz",
-				value:      []string{"foo", "bar"},
-				inList:     false,
-				inListFold: false,
+				name:           "not in list",
+				str:            "baz",
+				value:          []string{"foo", "bar"},
+				wantInList:     failure,
+				wantInListFold: failure,
 			},
 			{
-				name:       "different case",
-				str:        "FOO",
-				value:      []string{"foo", "bar"},
-				inList:     false,
-				inListFold: true,
+				name:           "different case",
+				str:            "FOO",
+				value:          []string{"foo", "bar"},
+				wantInList:     failure,
+				wantInListFold: success,
 			},
 		}
 
 		for _, tc := range cases {
 			reporter := newMockReporter(t)
 
-			if tc.inList {
-				NewString(reporter, tc.str).InList(tc.value...).
-					chain.assertNotFailed(t)
-				NewString(reporter, tc.str).NotInList(tc.value...).
-					chain.assertFailed(t)
-			} else {
-				NewString(reporter, tc.str).InList(tc.value...).
-					chain.assertFailed(t)
-				NewString(reporter, tc.str).NotInList(tc.value...).
-					chain.assertNotFailed(t)
-			}
+			NewString(reporter, tc.str).InList(tc.value...).
+				chain.assert(t, tc.wantInList)
+			NewString(reporter, tc.str).NotInList(tc.value...).
+				chain.assert(t, !tc.wantInList)
 
-			if tc.inListFold {
-				NewString(reporter, tc.str).InListFold(tc.value...).
-					chain.assertNotFailed(t)
-				NewString(reporter, tc.str).NotInListFold(tc.value...).
-					chain.assertFailed(t)
-			} else {
-				NewString(reporter, tc.str).InListFold(tc.value...).
-					chain.assertFailed(t)
-				NewString(reporter, tc.str).NotInListFold(tc.value...).
-					chain.assertNotFailed(t)
-			}
+			NewString(reporter, tc.str).InListFold(tc.value...).
+				chain.assert(t, tc.wantInListFold)
+			NewString(reporter, tc.str).NotInListFold(tc.value...).
+				chain.assert(t, !tc.wantInListFold)
 		}
 	})
 
 	t.Run("invalid argument", func(t *testing.T) {
 		reporter := newMockReporter(t)
 
-		NewString(reporter, "foo").
-			InList(). // empty list
-			chain.assertFailed(t)
-
-		NewString(reporter, "foo").
-			NotInList(). // empty list
-			chain.assertFailed(t)
-
-		NewString(reporter, "foo").
-			InListFold(). // empty list
-			chain.assertFailed(t)
+		NewString(reporter, "foo").InList().
+			chain.assert(t, failure)
+		NewString(reporter, "foo").NotInList().
+			chain.assert(t, failure)
 
-		NewString(reporter, "foo").
-			NotInListFold(). // empty list
-			chain.assertFailed(t)
+		NewString(reporter, "foo").InListFold().
+			chain.assert(t, failure)
+		NewString(reporter, "foo").NotInListFold().
+			chain.assert(t, failure)
 	})
 }
 
 func TestString_Contains(t *testing.T) {
 	cases := []struct {
-		name         string
-		str          string
-		value        string
-		contains     bool
-		containsFold bool
+		name             string
+		str              string
+		value            string
+		wantContains     chainResult
+		wantContainsFold chainResult
 	}{
 		{
-			name:         "contains",
-			str:          "11-foo-22",
-			value:        "foo",
-			contains:     true,
-			containsFold: true,
+			name:             "contains",
+			str:              "11-foo-22",
+			value:            "foo",
+			wantContains:     success,
+			wantContainsFold: success,
 		},
 		{
-			name:         "not contains",
-			str:          "11-foo-22",
-			value:        "bar",
-			contains:     false,
-			containsFold: false,
+			name:             "not contains",
+			str:              "11-foo-22",
+			value:            "bar",
+			wantContains:     failure,
+			wantContainsFold: failure,
 		},
 		{
-			name:         "different case",
-			str:          "11-foo-22",
-			value:        "FOO",
-			contains:     false,
-			containsFold: true,
+			name:             "different case",
+			str:              "11-foo-22",
+			value:            "FOO",
+			wantContains:     failure,
+			wantContainsFold: success,
 		},
 	}
 
 	for _, tc := range cases {
 		reporter := newMockReporter(t)
 
-		if tc.contains {
-			NewString(reporter, tc.str).Contains(tc.value).
-				chain.assertNotFailed(t)
-			NewString(reporter, tc.str).NotContains(tc.value).
-				chain.assertFailed(t)
-		} else {
-			NewString(reporter, tc.str).Contains(tc.value).
-				chain.assertFailed(t)
-			NewString(reporter, tc.str).NotContains(tc.value).
-				chain.assertNotFailed(t)
-		}
+		NewString(reporter, tc.str).Contains(tc.value).
+			chain.assert(t, tc.wantContains)
+		NewString(reporter, tc.str).NotContains(tc.value).
+			chain.assert(t, !tc.wantContains)
 
-		if tc.containsFold {
-			NewString(reporter, tc.str).ContainsFold(tc.value).
-				chain.assertNotFailed(t)
-			NewString(reporter, tc.str).NotContainsFold(tc.value).
-				chain.assertFailed(t)
-		} else {
-			NewString(reporter, tc.str).ContainsFold(tc.value).
-				chain.assertFailed(t)
-			NewString(reporter, tc.str).NotContainsFold(tc.value).
-				chain.assertNotFailed(t)
-		}
+		NewString(reporter, tc.str).ContainsFold(tc.value).
+			chain.assert(t, tc.wantContainsFold)
+		NewString(reporter, tc.str).NotContainsFold(tc.value).
+			chain.assert(t, !tc.wantContainsFold)
 	}
 }
 
 func TestString_HasPrefix(t *testing.T) {
 	cases := []struct {
-		name          string
-		str           string
-		value         string
-		hasPrefix     bool
-		hasPrefixFold bool
+		name              string
+		str               string
+		value             string
+		wantHasPrefix     chainResult
+		wantHasPrefixFold chainResult
 	}{
 		{
-			name:          "has prefix",
-			str:           "Hello World",
-			value:         "Hello",
-			hasPrefix:     true,
-			hasPrefixFold: true,
+			name:              "has prefix",
+			str:               "Hello World",
+			value:             "Hello",
+			wantHasPrefix:     success,
+			wantHasPrefixFold: success,
 		},
 		{
-			name:          "full match",
-			str:           "Hello World",
-			value:         "Hello World",
-			hasPrefix:     true,
-			hasPrefixFold: true,
+			name:              "full match",
+			str:               "Hello World",
+			value:             "Hello World",
+			wantHasPrefix:     success,
+			wantHasPrefixFold: success,
 		},
 		{
-			name:          "empty string",
-			str:           "Hello World",
-			value:         "",
-			hasPrefix:     true,
-			hasPrefixFold: true,
+			name:              "empty string",
+			str:               "Hello World",
+			value:             "",
+			wantHasPrefix:     success,
+			wantHasPrefixFold: success,
 		},
 		{
-			name:          "extra char",
-			str:           "Hello World",
-			value:         "Hello!",
-			hasPrefix:     false,
-			hasPrefixFold: false,
+			name:              "extra char",
+			str:               "Hello World",
+			value:             "Hello!",
+			wantHasPrefix:     failure,
+			wantHasPrefixFold: failure,
 		},
 		{
-			name:          "different case",
-			str:           "Hello World",
-			value:         "hell",
-			hasPrefix:     false,
-			hasPrefixFold: true,
+			name:              "different case",
+			str:               "Hello World",
+			value:             "hell",
+			wantHasPrefix:     failure,
+			wantHasPrefixFold: success,
 		},
 		{
-			name:          "different case extra char",
-			str:           "Hello World",
-			value:         "hella",
-			hasPrefix:     false,
-			hasPrefixFold: false,
+			name:              "different case extra char",
+			str:               "Hello World",
+			value:             "hella",
+			wantHasPrefix:     failure,
+			wantHasPrefixFold: failure,
 		},
 		{
-			name:          "different case full match",
-			str:           "Hello World",
-			value:         "hELLO wORLD",
-			hasPrefix:     false,
-			hasPrefixFold: true,
+			name:              "different case full match",
+			str:               "Hello World",
+			value:             "hELLO wORLD",
+			wantHasPrefix:     failure,
+			wantHasPrefixFold: success,
 		},
 	}
 
 	for _, tc := range cases {
 		reporter := newMockReporter(t)
 
-		if tc.hasPrefix {
-			NewString(reporter, tc.str).HasPrefix(tc.value).
-				chain.assertNotFailed(t)
-			NewString(reporter, tc.str).NotHasPrefix(tc.value).
-				chain.assertFailed(t)
-		} else {
-			NewString(reporter, tc.str).HasPrefix(tc.value).
-				chain.assertFailed(t)
-			NewString(reporter, tc.str).NotHasPrefix(tc.value).
-				chain.assertNotFailed(t)
-		}
+		NewString(reporter, tc.str).HasPrefix(tc.value).
+			chain.assert(t, tc.wantHasPrefix)
+		NewString(reporter, tc.str).NotHasPrefix(tc.value).
+			chain.assert(t, !tc.wantHasPrefix)
 
-		if tc.hasPrefixFold {
-			NewString(reporter, tc.str).HasPrefixFold(tc.value).
-				chain.assertNotFailed(t)
-			NewString(reporter, tc.str).NotHasPrefixFold(tc.value).
-				chain.assertFailed(t)
-		} else {
-			NewString(reporter, tc.str).HasPrefixFold(tc.value).
-				chain.assertFailed(t)
-			NewString(reporter, tc.str).NotHasPrefixFold(tc.value).
-				chain.assertNotFailed(t)
-		}
+		NewString(reporter, tc.str).HasPrefixFold(tc.value).
+			chain.assert(t, tc.wantHasPrefixFold)
+		NewString(reporter, tc.str).NotHasPrefixFold(tc.value).
+			chain.assert(t, !tc.wantHasPrefixFold)
 	}
 }
 
 func TestString_HasSuffix(t *testing.T) {
 	cases := []struct {
-		name          string
-		str           string
-		value         string
-		hasSuffix     bool
-		hasSuffixFold bool
+		name              string
+		str               string
+		value             string
+		wantHasSuffix     chainResult
+		wantHasSuffixFold chainResult
 	}{
 		{
-			name:          "has suffix",
-			str:           "Hello World",
-			value:         "World",
-			hasSuffix:     true,
-			hasSuffixFold: true,
+			name:              "has suffix",
+			str:               "Hello World",
+			value:             "World",
+			wantHasSuffix:     success,
+			wantHasSuffixFold: success,
 		},
 		{
-			name:          "full match",
-			str:           "Hello World",
-			value:         "Hello World",
-			hasSuffix:     true,
-			hasSuffixFold: true,
+			name:              "full match",
+			str:               "Hello World",
+			value:             "Hello World",
+			wantHasSuffix:     success,
+			wantHasSuffixFold: success,
 		},
 		{
-			name:          "empty string",
-			str:           "Hello World",
-			value:         "",
-			hasSuffix:     true,
-			hasSuffixFold: true,
+			name:              "empty string",
+			str:               "Hello World",
+			value:             "",
+			wantHasSuffix:     success,
+			wantHasSuffixFold: success,
 		},
 		{
-			name:          "extra char",
-			str:           "Hello World",
-			value:         "!World",
-			hasSuffix:     false,
-			hasSuffixFold: false,
+			name:              "extra char",
+			str:               "Hello World",
+			value:             "!World",
+			wantHasSuffix:     failure,
+			wantHasSuffixFold: failure,
 		},
 		{
-			name:          "different case",
-			str:           "Hello World",
-			value:         "WORLD",
-			hasSuffix:     false,
-			hasSuffixFold: true,
+			name:              "different case",
+			str:               "Hello World",
+			value:             "WORLD",
+			wantHasSuffix:     failure,
+			wantHasSuffixFold: success,
 		},
 		{
-			name:          "different case extra char",
-			str:           "Hello World",
-			value:         "!WORLD",
-			hasSuffix:     false,
-			hasSuffixFold: false,
+			name:              "different case extra char",
+			str:               "Hello World",
+			value:             "!WORLD",
+			wantHasSuffix:     failure,
+			wantHasSuffixFold: failure,
 		},
 		{
-			name:          "different case full match",
-			str:           "Hello World",
-			value:         "hELLO wORLD",
-			hasSuffix:     false,
-			hasSuffixFold: true,
+			name:              "different case full match",
+			str:               "Hello World",
+			value:             "hELLO wORLD",
+			wantHasSuffix:     failure,
+			wantHasSuffixFold: success,
 		},
 	}
 
 	for _, tc := range cases {
 		reporter := newMockReporter(t)
 
-		if tc.hasSuffix {
-			NewString(reporter, tc.str).HasSuffix(tc.value).
-				chain.assertNotFailed(t)
-			NewString(reporter, tc.str).NotHasSuffix(tc.value).
-				chain.assertFailed(t)
-		} else {
-			NewString(reporter, tc.str).HasSuffix(tc.value).
-				chain.assertFailed(t)
-			NewString(reporter, tc.str).NotHasSuffix(tc.value).
-				chain.assertNotFailed(t)
-		}
+		NewString(reporter, tc.str).HasSuffix(tc.value).
+			chain.assert(t, tc.wantHasSuffix)
+		NewString(reporter, tc.str).NotHasSuffix(tc.value).
+			chain.assert(t, !tc.wantHasSuffix)
 
-		if tc.hasSuffixFold {
-			NewString(reporter, tc.str).HasSuffixFold(tc.value).
-				chain.assertNotFailed(t)
-			NewString(reporter, tc.str).NotHasSuffixFold(tc.value).
-				chain.assertFailed(t)
-		} else {
-			NewString(reporter, tc.str).HasSuffixFold(tc.value).
-				chain.assertFailed(t)
-			NewString(reporter, tc.str).NotHasSuffixFold(tc.value).
-				chain.assertNotFailed(t)
-		}
+		NewString(reporter, tc.str).HasSuffixFold(tc.value).
+			chain.assert(t, tc.wantHasSuffixFold)
+		NewString(reporter, tc.str).NotHasSuffixFold(tc.value).
+			chain.assert(t, !tc.wantHasSuffixFold)
 	}
 }
 
@@ -595,7 +512,7 @@ func TestString_Match(t *testing.T) {
 		value := NewString(reporter, "http://example.com/users/john")
 
 		m := value.Match(`http://(?P<host>.+)/users/(?P<user>.+)`)
-		m.chain.assertNotFailed(t)
+		m.chain.assert(t, success)
 
 		assert.Equal(t,
 			[]string{"http://example.com/users/john", "example.com", "john"},
@@ -608,7 +525,7 @@ func TestString_Match(t *testing.T) {
 		value := NewString(reporter, "http://example.com/users/john")
 
 		m := value.Match(`http://(.+)/users/(.+)`)
-		m.chain.assertNotFailed(t)
+		m.chain.assert(t, success)
 
 		assert.Equal(t,
 			[]string{"http://example.com/users/john", "example.com", "john"},
@@ -625,8 +542,8 @@ func TestString_Match(t *testing.T) {
 
 		assert.Equal(t, 2, len(m))
 
-		m[0].chain.assertNotFailed(t)
-		m[1].chain.assertNotFailed(t)
+		m[0].chain.assert(t, success)
+		m[1].chain.assert(t, success)
 
 		assert.Equal(t,
 			[]string{"http://example.com/users/john", "example.com", "john"},
@@ -639,28 +556,28 @@ func TestString_Match(t *testing.T) {
 
 	t.Run("status", func(t *testing.T) {
 		cases := []struct {
-			str      string
-			re       string
-			match    bool
-			notMatch bool
+			str          string
+			re           string
+			wantMatch    chainResult
+			wantNotMatch chainResult
 		}{
 			{
-				str:      `a`,
-				re:       `a`,
-				match:    true,
-				notMatch: false,
+				str:          `a`,
+				re:           `a`,
+				wantMatch:    success,
+				wantNotMatch: failure,
 			},
 			{
-				str:      `a`,
-				re:       `[^a]`,
-				match:    false,
-				notMatch: true,
+				str:          `a`,
+				re:           `[^a]`,
+				wantMatch:    failure,
+				wantNotMatch: success,
 			},
 			{
-				str:      `a`,
-				re:       `[`,
-				match:    false,
-				notMatch: false,
+				str:          `a`,
+				re:           `[`,
+				wantMatch:    failure,
+				wantNotMatch: failure,
 			},
 		}
 
@@ -670,33 +587,17 @@ func TestString_Match(t *testing.T) {
 
 				var value *String
 
-				if tc.match {
-					value = NewString(reporter, tc.str)
-					value.Match(tc.re).chain.assertNotFailed(t)
-					value.chain.assertNotFailed(t)
-
-					value = NewString(reporter, tc.str)
-					value.MatchAll(tc.re)
-					value.chain.assertNotFailed(t)
-				} else {
-					value = NewString(reporter, tc.str)
-					value.Match(tc.re).chain.assertFailed(t)
-					value.chain.assertFailed(t)
-
-					value = NewString(reporter, tc.str)
-					value.MatchAll(tc.re)
-					value.chain.assertFailed(t)
-				}
-
-				if tc.notMatch {
-					value = NewString(reporter, tc.str)
-					value.NotMatch(tc.re)
-					value.chain.assertNotFailed(t)
-				} else {
-					value = NewString(reporter, tc.str)
-					value.NotMatch(tc.re)
-					value.chain.assertFailed(t)
-				}
+				value = NewString(reporter, tc.str)
+				value.Match(tc.re).chain.assert(t, tc.wantMatch)
+				value.chain.assert(t, tc.wantMatch)
+
+				value = NewString(reporter, tc.str)
+				value.MatchAll(tc.re)
+				value.chain.assert(t, tc.wantMatch)
+
+				value = NewString(reporter, tc.str)
+				value.NotMatch(tc.re)
+				value.chain.assert(t, tc.wantNotMatch)
 			})
 		}
 	})
@@ -704,31 +605,25 @@ func TestString_Match(t *testing.T) {
 
 func TestString_IsAscii(t *testing.T) {
 	cases := []struct {
-		str     string
-		isASCII bool
+		str         string
+		wantIsASCII chainResult
 	}{
-		{"Ascii", true},
-		{string(rune(127)), true},
-		{"Ascii is アスキー", false},
-		{"アスキー", false},
-		{string(rune(128)), false},
+		{"Ascii", success},
+		{string(rune(127)), success},
+		{"Ascii is アスキー", failure},
+		{"アスキー", failure},
+		{string(rune(128)), failure},
 	}
 
 	for _, tc := range cases {
 		t.Run(tc.str, func(t *testing.T) {
 			reporter := newMockReporter(t)
 
-			if tc.isASCII {
-				NewString(reporter, tc.str).IsASCII().
-					chain.assertNotFailed(t)
-				NewString(reporter, tc.str).NotASCII().
-					chain.assertFailed(t)
-			} else {
-				NewString(reporter, tc.str).IsASCII().
-					chain.assertFailed(t)
-				NewString(reporter, tc.str).NotASCII().
-					chain.assertNotFailed(t)
-			}
+			NewString(reporter, tc.str).IsASCII().
+				chain.assert(t, tc.wantIsASCII)
+
+			NewString(reporter, tc.str).NotASCII().
+				chain.assert(t, !tc.wantIsASCII)
 		})
 	}
 }
@@ -738,90 +633,88 @@ func TestString_AsNumber(t *testing.T) {
 		name        string
 		str         string
 		base        []int
-		fail        bool
+		result      chainResult
 		expectedNum float64
 	}{
 		{
-			name:        "default_base_integer",
+			name:        "default base integer",
 			str:         "1234567",
-			fail:        false,
+			result:      success,
 			expectedNum: float64(1234567),
 		},
 		{
-			name:        "default_base_float",
+			name:        "default base float",
 			str:         "11.22",
-			fail:        false,
+			result:      success,
 			expectedNum: float64(11.22),
 		},
 		{
-			name:        "default_base_bad",
-			str:         "a1",
-			fail:        true,
-			expectedNum: 0,
+			name:   "default base bad",
+			str:    "a1",
+			result: failure,
 		},
 		{
-			name:        "base10_integer",
+			name:        "base10 integer",
 			str:         "100",
 			base:        []int{10},
-			fail:        false,
+			result:      success,
 			expectedNum: float64(100),
 		},
 		{
-			name:        "base10_float",
+			name:        "base10 float",
 			str:         "11.22",
 			base:        []int{10},
-			fail:        false,
+			result:      success,
 			expectedNum: float64(11.22),
 		},
 		{
-			name:        "base16_integer",
+			name:        "base16 integer",
 			str:         "100",
 			base:        []int{16},
-			fail:        false,
+			result:      success,
 			expectedNum: float64(0x100),
 		},
 		{
-			name:        "base16_float",
-			str:         "11.22",
-			base:        []int{16},
-			fail:        true,
-			expectedNum: 0,
+			name:   "base16 float",
+			str:    "11.22",
+			base:   []int{16},
+			result: failure,
 		},
 		{
-			name:        "base16_large_integer",
+			name:        "base16 large integer",
 			str:         "4000000000000000",
 			base:        []int{16},
-			fail:        false,
+			result:      success,
 			expectedNum: float64(0x4000000000000000),
 		},
 		{
-			name: "default_float_precision_max",
-			str:  "4611686018427387905",
-			fail: true,
+			name:   "default float precision max",
+			str:    "4611686018427387905",
+			result: failure,
 		},
 		{
-			name: "base10_float_precision_max",
-			str:  "4611686018427387905",
-			base: []int{10},
-			fail: true,
+			name:   "base10 float precision max",
+			str:    "4611686018427387905",
+			base:   []int{10},
+			result: failure,
 		},
 		{
-			name: "base16_float_precision_max",
-			str:  "8000000000000001",
-			base: []int{16},
-			fail: true,
+			name:   "base16 float precision max",
+			str:    "8000000000000001",
+			base:   []int{16},
+			result: failure,
 		},
 		{
-			name: "base16_float_precision_min",
-			str:  "-4000000000000001",
-			base: []int{16},
-			fail: true,
+			name:   "base16 float precision min",
+			str:    "-4000000000000001",
+			base:   []int{16},
+			result: failure,
 		},
 		{
-			name: "multiple_base",
-			str:  "100",
-			base: []int{10, 16},
-			fail: true,
+			name:   "multiple bases",
+			str:    "100",
+			base:   []int{10, 16},
+			result: failure,
 		},
 	}
 
@@ -832,14 +725,13 @@ func TestString_AsNumber(t *testing.T) {
 			str := NewString(reporter, tc.str)
 			num := str.AsNumber(tc.base...)
 
-			if tc.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)
+			str.chain.assert(t, tc.result)
+			num.chain.assert(t, tc.result)
+
+			if tc.result {
 				assert.Equal(t, tc.expectedNum, num.Raw())
+			} else {
+				assert.Equal(t, float64(0), num.Raw())
 			}
 		})
 	}
@@ -856,7 +748,7 @@ func TestString_AsBoolean(t *testing.T) {
 			value := NewString(reporter, str)
 
 			b := value.AsBoolean()
-			b.chain.assertNotFailed(t)
+			b.chain.assert(t, success)
 
 			assert.True(t, b.Raw())
 		})
@@ -868,7 +760,7 @@ func TestString_AsBoolean(t *testing.T) {
 			value := NewString(reporter, str)
 
 			b := value.AsBoolean()
-			b.chain.assertNotFailed(t)
+			b.chain.assert(t, success)
 
 			assert.False(t, b.Raw())
 		})
@@ -880,19 +772,19 @@ func TestString_AsBoolean(t *testing.T) {
 			value := NewString(reporter, str)
 
 			b := value.AsBoolean()
-			b.chain.assertFailed(t)
+			b.chain.assert(t, failure)
 		})
 	}
 }
 
 func TestString_AsDateTime(t *testing.T) {
-	t.Run("default_formats_RFC1123+GMT", func(t *testing.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)
+		value.chain.assert(t, success)
+		dt.chain.assert(t, success)
 
 		assert.True(t, time.Date(1994, 11, 15, 8, 12, 31, 0, time.UTC).Equal(dt.Raw()))
 	})
@@ -902,19 +794,19 @@ func TestString_AsDateTime(t *testing.T) {
 		value := NewString(reporter, "15 Nov 94 08:12 GMT")
 
 		dt := value.AsDateTime(time.RFC822)
-		value.chain.assertNotFailed(t)
-		dt.chain.assertNotFailed(t)
+		value.chain.assert(t, success)
+		dt.chain.assert(t, success)
 
 		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) {
+	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)
+		value.chain.assert(t, failure)
+		dt.chain.assert(t, failure)
 
 		assert.True(t, time.Unix(0, 0).Equal(dt.Raw()))
 	})
@@ -934,45 +826,45 @@ func TestString_AsDateTime(t *testing.T) {
 	}
 
 	for n, f := range formats {
-		t.Run("default_formats_"+f, func(t *testing.T) {
+		t.Run("default formats - "+f, func(t *testing.T) {
 			str := time.Now().Format(f)
 
 			reporter := newMockReporter(t)
 			value := NewString(reporter, str)
 
 			dt := value.AsDateTime()
-			dt.chain.assertNotFailed(t)
+			dt.chain.assert(t, success)
 		})
 
-		t.Run("all_formats_"+f, func(t *testing.T) {
+		t.Run("all formats - "+f, func(t *testing.T) {
 			str := time.Now().Format(f)
 
 			reporter := newMockReporter(t)
 			value := NewString(reporter, str)
 
 			dt := value.AsDateTime(formats...)
-			dt.chain.assertNotFailed(t)
+			dt.chain.assert(t, success)
 		})
 
-		t.Run("same_format_"+f, func(t *testing.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)
+			dt.chain.assert(t, success)
 		})
 
 		if n != 0 {
-			t.Run("different_format_"+f, func(t *testing.T) {
+			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)
+				dt.chain.assert(t, failure)
 			})
 		}
 	}
diff --git a/value_test.go b/value_test.go
index 29fd382..4f17f76 100644
--- a/value_test.go
+++ b/value_test.go
@@ -11,7 +11,7 @@ import (
 )
 
 func TestValue_FailedChain(t *testing.T) {
-	chain := newFailedChain(t)
+	chain := newMockChain(t, flagFailed)
 
 	value := newValue(chain, nil)
 	value.chain.assert(t, failure)
diff --git a/websocket_message_test.go b/websocket_message_test.go
index 7d7f568..82ef323 100644
--- a/websocket_message_test.go
+++ b/websocket_message_test.go
@@ -9,8 +9,7 @@ import (
 )
 
 func TestWebsocketMessage_Failed(t *testing.T) {
-	chain := newMockChain(t)
-	chain.setFailed()
+	chain := newMockChain(t, flagFailed)
 
 	msg := newEmptyWebsocketMessage(chain)
 
diff --git a/websocket_test.go b/websocket_test.go
index 949f67a..f106923 100644
--- a/websocket_test.go
+++ b/websocket_test.go
@@ -13,10 +13,8 @@ func noWsPreSteps(ws *Websocket) {}
 
 func TestWebsocket_FailedChain(t *testing.T) {
 	reporter := newMockReporter(t)
-	chain := newChainWithDefaults("test", reporter)
 	config := newMockConfig(reporter)
-
-	chain.setFailed()
+	chain := newChainWithDefaults("test", reporter, flagFailed)
 
 	ws := newWebsocket(chain, config, nil)
 
@@ -128,9 +126,9 @@ func TestWebsocket_MockConn(t *testing.T) {
 
 func TestWebsocket_Expect(t *testing.T) {
 	type args struct {
-		failedChain bool
-		wsConn      WebsocketConn
-		wsPreSteps  func(*Websocket)
+		chainFlags chainFlags
+		wsConn     WebsocketConn
+		wsPreSteps func(*Websocket)
 	}
 	cases := []struct {
 		name     string
@@ -158,9 +156,9 @@ func TestWebsocket_Expect(t *testing.T) {
 		{
 			name: "chain already failed",
 			args: args{
-				failedChain: true,
-				wsPreSteps:  noWsPreSteps,
-				wsConn:      &mockWebsocketConn{},
+				chainFlags: flagFailed,
+				wsPreSteps: noWsPreSteps,
+				wsConn:     &mockWebsocketConn{},
 			},
 			assertOk: false,
 		},
@@ -196,12 +194,8 @@ func TestWebsocket_Expect(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			reporter := newMockReporter(t)
-			chain := newChainWithDefaults("test", reporter)
 			config := newMockConfig(reporter)
-
-			if tc.args.failedChain {
-				chain.setFailed()
-			}
+			chain := newChainWithDefaults("test", reporter, tc.args.chainFlags)
 
 			ws := newWebsocket(chain, config, tc.args.wsConn)
 			tc.args.wsPreSteps(ws)