Repository: gavv/httpexpect
Pull request #331 (2023-03-05 06:30:59)

Convert String tests to table-driven style
Issue: https://github.com/gavv/httpexpect/issues/330

Convert each subtest of the following tests to table-driven style:

- TestString_IsEqual
- TestString_IsEqualFold
- TestString_InList
- TestString_InListFold
- TestString_Contains
- TestString_ContainsFold
- TestString_HasPrefix
- TestString_HasSuffix
- TestString_HasPrefixFold
- TestString_HasSuffixFold

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

Coverage: 95.056%. Remained the same when pulling **00ba01dbe08d94ab2576d9296bbec42a3a3295e3 on neverbeenthisweeb:330-string-tests-tdd** into **92cc0f7462ed9ff7aab7231884e8d7d2d602a089 on gavv:master**.


Patch:
diff --git a/_examples/go.mod b/_examples/go.mod
index 6c03e20..15ceec9 100644
--- a/_examples/go.mod
+++ b/_examples/go.mod
@@ -5,7 +5,7 @@ go 1.17
 require (
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/fasthttp/websocket v1.5.0
-	github.com/gavv/httpexpect/v2 v2.13.0
+	github.com/gavv/httpexpect/v2 v2.14.0
 	github.com/gin-gonic/gin v1.8.1
 	github.com/go-oauth2/oauth2/v4 v4.5.1
 	github.com/gorilla/websocket v1.5.0
@@ -86,9 +86,9 @@ require (
 	github.com/yudai/gojsondiff v1.0.0 // indirect
 	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
 	golang.org/x/crypto v0.3.0 // indirect
-	golang.org/x/net v0.7.0 // indirect
-	golang.org/x/sys v0.5.0 // indirect
-	golang.org/x/text v0.7.0 // indirect
+	golang.org/x/net v0.8.0 // indirect
+	golang.org/x/sys v0.6.0 // indirect
+	golang.org/x/text v0.8.0 // indirect
 	golang.org/x/time v0.2.0 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
diff --git a/_examples/go.sum b/_examples/go.sum
index dd96664..df0576e 100644
--- a/_examples/go.sum
+++ b/_examples/go.sum
@@ -53,8 +53,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
-github.com/gavv/httpexpect/v2 v2.13.0 h1:d9bGxLIjUpi0m1RXwxmvY7WPwhBqHQqr1gnLhB+q1ls=
-github.com/gavv/httpexpect/v2 v2.13.0/go.mod h1:Cw5O0RmjQOmUewnAPTnwhXHGmO2vXGjw07vFOq2jYRg=
+github.com/gavv/httpexpect/v2 v2.14.0 h1:rWM60bPJpVcIZWgubYDvipTeHdJlseDM5hovR+wgFVo=
+github.com/gavv/httpexpect/v2 v2.14.0/go.mod h1:lWjY74mDnFXjTqqK41HouBDDCANFo1yrejZ5VraAE1k=
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
@@ -353,6 +353,7 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -377,8 +378,10 @@ golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfS
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
-golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
 golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
@@ -388,6 +391,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -421,14 +425,16 @@ golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
 golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -437,8 +443,9 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
 golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -448,6 +455,7 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/formatter.go b/formatter.go
index 8584bc7..aaf53dc 100644
--- a/formatter.go
+++ b/formatter.go
@@ -244,6 +244,8 @@ func (f *DefaultFormatter) fillDescription(
 func (f *DefaultFormatter) fillErrors(
 	data *FormatData, ctx *AssertionContext, failure *AssertionFailure,
 ) {
+	data.Errors = []string{}
+
 	for _, err := range failure.Errors {
 		if refIsNil(err) {
 			continue


Test patch:
diff --git a/assertion_test.go b/assertion_test.go
index b8c7957..24920e6 100644
--- a/assertion_test.go
+++ b/assertion_test.go
@@ -8,12 +8,6 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-type typedErrorNil int
-
-func (*typedErrorNil) Error() string {
-	return ""
-}
-
 func TestAssertion_Handler(t *testing.T) {
 	type test struct {
 		formatter *mockFormatter
@@ -376,11 +370,11 @@ func TestAssertion_ValidateTraits(t *testing.T) {
 }
 
 func TestAssertion_ValidateAssertion(t *testing.T) {
-	var tnil *typedErrorNil
-	var tnilPtr error = tnil
+	var mErr *mockError
+	var mErrPtr error = mErr
 
-	assert.Nil(t, tnilPtr)
-	assert.NotEqual(t, nil, tnilPtr)
+	assert.Nil(t, mErrPtr)
+	assert.NotEqual(t, nil, mErrPtr)
 
 	tests := []struct {
 		testName          string
@@ -426,7 +420,7 @@ func TestAssertion_ValidateAssertion(t *testing.T) {
 			errorContainsText: "Errors",
 			input: AssertionFailure{
 				Type:   AssertOperation,
-				Errors: []error{tnilPtr},
+				Errors: []error{mErrPtr},
 			},
 		},
 		{
diff --git a/formatter_test.go b/formatter_test.go
index 5f83540..46df0b3 100644
--- a/formatter_test.go
+++ b/formatter_test.go
@@ -472,6 +472,70 @@ func TestFormatter_FailureDelta(t *testing.T) {
 	}
 }
 
+func TestFormatter_FailureErrors(t *testing.T) {
+	var mErr *mockError
+	var mErrPtr error = mErr
+
+	assert.Nil(t, mErrPtr)
+	assert.NotEqual(t, nil, mErrPtr)
+
+	cases := []struct {
+		name     string
+		errors   []error
+		expected []string
+	}{
+		{
+			name:     "nil errors slice",
+			errors:   nil,
+			expected: []string{},
+		},
+		{
+			name:     "empty errors slice",
+			errors:   []error{},
+			expected: []string{},
+		},
+		{
+			name:     "errors slice with nil error",
+			errors:   []error{nil},
+			expected: []string{},
+		},
+		{
+			name:     "errors slice with typed nil error",
+			errors:   []error{mErrPtr},
+			expected: []string{},
+		},
+		{
+			name:     "errors slice with one error",
+			errors:   []error{fmt.Errorf("error message")},
+			expected: []string{"error message"},
+		},
+		{
+			name: "errors slice with multiple errors",
+			errors: []error{
+				fmt.Errorf("error message 1"),
+				fmt.Errorf("error message 2"),
+			},
+			expected: []string{
+				"error message 1",
+				"error message 2",
+			},
+		},
+	}
+
+	df := &DefaultFormatter{}
+	ctx := &AssertionContext{}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			fl := &AssertionFailure{
+				Errors: tc.errors,
+			}
+			fd := df.buildFormatData(ctx, fl)
+			assert.Equal(t, tc.expected, fd.Errors)
+		})
+	}
+}
+
 func TestFormatter_FloatFormat(t *testing.T) {
 	type testCase struct {
 		name     string
diff --git a/mocks_test.go b/mocks_test.go
index 2a2b4f8..0231cd6 100644
--- a/mocks_test.go
+++ b/mocks_test.go
@@ -403,3 +403,9 @@ func (e *mockNetError) Timeout() bool {
 func (e *mockNetError) Temporary() bool {
 	return e.isTemporary
 }
+
+type mockError struct{}
+
+func (e *mockError) Error() string {
+	return ""
+}
diff --git a/string_test.go b/string_test.go
index 8960829..1c6b694 100644
--- a/string_test.go
+++ b/string_test.go
@@ -213,341 +213,595 @@ func TestString_IsEmpty(t *testing.T) {
 }
 
 func TestString_IsEqual(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value := NewString(reporter, "foo")
-
-	assert.Equal(t, "foo", value.Raw())
-
-	value.IsEqual("foo")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.IsEqual("FOO")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	cases := []struct {
+		name    string
+		str     string
+		value   string
+		isEqual bool
+	}{
+		{
+			name:    "equivalent string",
+			str:     "foo",
+			value:   "foo",
+			isEqual: true,
+		},
+		{
+			name:    "non-equivalent string",
+			str:     "foo",
+			value:   "FOO",
+			isEqual: false,
+		},
+	}
 
-	value.NotEqual("FOO")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	for _, tc := range cases {
+		reporter := newMockReporter(t)
 
-	value.NotEqual("foo")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+		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).
+				NotEqual(tc.value).
+				chain.assertNotFailed(t)
+			NewString(reporter, tc.str).
+				IsEqual(tc.value).
+				chain.assertFailed(t)
+		}
+	}
 }
 
 func TestString_IsEqualFold(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value := NewString(reporter, "foo")
-
-	value.IsEqualFold("foo")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.IsEqualFold("FOO")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.IsEqualFold("foo2")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.NotEqualFold("foo")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	cases := []struct {
+		name        string
+		str         string
+		value       string
+		isEqualFold bool
+	}{
+		{
+			name:        "equivalent string",
+			str:         "foo",
+			value:       "foo",
+			isEqualFold: true,
+		},
+		{
+			name:        "equivalent string because case insensitive",
+			str:         "foo",
+			value:       "FOO",
+			isEqualFold: true,
+		},
+		{
+			name:        "non-equivalent string",
+			str:         "foo",
+			value:       "foo2",
+			isEqualFold: false,
+		},
+	}
 
-	value.NotEqualFold("FOO")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	for _, tc := range cases {
+		reporter := newMockReporter(t)
 
-	value.NotEqualFold("foo2")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+		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).
+				NotEqualFold(tc.value).
+				chain.assertNotFailed(t)
+			NewString(reporter, tc.str).
+				IsEqualFold(tc.value).
+				chain.assertFailed(t)
+		}
+	}
 }
 
 func TestString_InList(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value := NewString(reporter, "foo")
-
-	assert.Equal(t, "foo", value.Raw())
-
-	value.InList()
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	t.Run("basic", func(t *testing.T) {
+		cases := []struct {
+			name     string
+			str      string
+			value    []string
+			isInList bool
+		}{
+			{
+				name:     "in list",
+				str:      "foo",
+				value:    []string{"foo", "bar"},
+				isInList: true,
+			},
+			{
+				name:     "not in list because case sensitive",
+				str:      "foo",
+				value:    []string{"FOO", "BAR"},
+				isInList: false,
+			},
+			{
+				name:     "not in list",
+				str:      "foo",
+				value:    []string{"FOO", "bar"},
+				isInList: false,
+			},
+			{
+				name:     "in list",
+				str:      "foo",
+				value:    []string{"foo", "BAR"},
+				isInList: true,
+			},
+		}
 
-	value.NotInList()
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+		for _, tc := range cases {
+			reporter := newMockReporter(t)
 
-	value.InList("foo", "bar")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+			if tc.isInList {
+				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).
+					NotInList(tc.value...).
+					chain.assertNotFailed(t)
+				NewString(reporter, tc.str).
+					InList(tc.value...).
+					chain.assertFailed(t)
+			}
+		}
+	})
 
-	value.InList("FOO", "BAR")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	t.Run("invalid argument", func(t *testing.T) {
+		reporter := newMockReporter(t)
 
-	value.NotInList("FOO", "bar")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+		NewString(reporter, "foo").
+			InList(). // empty list
+			chain.assertFailed(t)
 
-	value.NotInList("foo", "BAR")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+		NewString(reporter, "foo").
+			NotInList(). // empty list
+			chain.assertFailed(t)
+	})
 }
 
 func TestString_InListFold(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value := NewString(reporter, "Foo")
-
-	assert.Equal(t, "Foo", value.Raw())
-
-	value.InListFold()
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.NotInListFold()
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.InListFold("foo", "bar")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	t.Run("basic", func(t *testing.T) {
+		cases := []struct {
+			name         string
+			str          string
+			value        []string
+			isInListFold bool
+		}{
+			{
+				name:         "in list",
+				str:          "foo",
+				value:        []string{"foo", "bar"},
+				isInListFold: true,
+			},
+			{
+				name:         "in list because case insensitive",
+				str:          "foo",
+				value:        []string{"FOO", "BAR"},
+				isInListFold: true,
+			},
+			{
+				name:         "not in list",
+				str:          "foo",
+				value:        []string{"BAR", "BAZ"},
+				isInListFold: false,
+			},
+		}
 
-	value.InListFold("FOO", "BAR")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+		for _, tc := range cases {
+			reporter := newMockReporter(t)
 
-	value.InListFold("BAR", "BAZ")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+			if tc.isInListFold {
+				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).
+					NotInListFold(tc.value...).
+					chain.assertNotFailed(t)
+				NewString(reporter, tc.str).
+					InListFold(tc.value...).
+					chain.assertFailed(t)
+			}
+		}
+	})
 
-	value.NotInListFold("foo", "bar")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	t.Run("invalid argument", func(t *testing.T) {
+		reporter := newMockReporter(t)
 
-	value.NotInListFold("FOO", "BAR")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+		NewString(reporter, "foo").
+			InListFold(). // empty list
+			chain.assertFailed(t)
 
-	value.NotInListFold("BAR", "BAZ")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+		NewString(reporter, "foo").
+			NotInListFold(). // empty list
+			chain.assertFailed(t)
+	})
 }
 
 func TestString_Contains(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value := NewString(reporter, "11-foo-22")
-
-	value.Contains("foo")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.Contains("FOO")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	cases := []struct {
+		name       string
+		str        string
+		value      string
+		isContains bool
+	}{
+		{
+			name:       "contains",
+			str:        "11-foo-22",
+			value:      "foo",
+			isContains: true,
+		},
+		{
+			name:       "not contains",
+			str:        "11-foo-22",
+			value:      "FOO",
+			isContains: false,
+		},
+	}
 
-	value.NotContains("FOO")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	for _, tc := range cases {
+		reporter := newMockReporter(t)
 
-	value.NotContains("foo")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+		if tc.isContains {
+			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).
+				NotContains(tc.value).
+				chain.assertNotFailed(t)
+			NewString(reporter, tc.str).
+				Contains(tc.value).
+				chain.assertFailed(t)
+		}
+	}
 }
 
 func TestString_ContainsFold(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value := NewString(reporter, "11-foo-22")
-
-	value.ContainsFold("foo")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.ContainsFold("FOO")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.ContainsFold("foo3")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.NotContainsFold("foo")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	cases := []struct {
+		name           string
+		str            string
+		value          string
+		isContainsFold bool
+	}{
+		{
+			name:           "contains",
+			str:            "11-foo-22",
+			value:          "foo",
+			isContainsFold: true,
+		},
+		{
+			name:           "contains because case insensitive",
+			str:            "11-foo-22",
+			value:          "FOO",
+			isContainsFold: true,
+		},
+		{
+			name:           "not contains",
+			str:            "11-foo-22",
+			value:          "foo3",
+			isContainsFold: false,
+		},
+	}
 
-	value.NotContainsFold("FOO")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	for _, tc := range cases {
+		reporter := newMockReporter(t)
 
-	value.NotContainsFold("foo3")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+		if tc.isContainsFold {
+			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).
+				NotContainsFold(tc.value).
+				chain.assertNotFailed(t)
+			NewString(reporter, tc.str).
+				ContainsFold(tc.value).
+				chain.assertFailed(t)
+		}
+	}
 }
 
 func TestString_HasPrefix(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value := NewString(reporter, "Hello World")
-
-	value.HasPrefix("Hello")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.HasPrefix("Hello World")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.HasPrefix("")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.HasPrefix("Hello!")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.HasPrefix("hello")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.HasPrefix("World")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.NotHasPrefix("Bye")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	cases := []struct {
+		name        string
+		str         string
+		value       string
+		isHasPrefix bool
+	}{
+		{
+			name:        "has prefix",
+			str:         "Hello World",
+			value:       "Hello",
+			isHasPrefix: true,
+		},
+		{
+			name:        "full sentence",
+			str:         "Hello World",
+			value:       "Hello World",
+			isHasPrefix: true,
+		},
+		{
+			name:        "empty string",
+			str:         "Hello World",
+			value:       "",
+			isHasPrefix: true,
+		},
+		{
+			name:        "not has prefix because value has extra char",
+			str:         "Hello World",
+			value:       "Hello!",
+			isHasPrefix: false,
+		},
+		{
+			name:        "not has prefix because case sensitive",
+			str:         "Hello World",
+			value:       "hello",
+			isHasPrefix: false,
+		},
+		{
+			name:        "not has prefix because value is suffix",
+			str:         "Hello World",
+			value:       "World",
+			isHasPrefix: false,
+		},
+		{
+			name:        "not has prefix because value does not exist in sentence",
+			str:         "Hello World",
+			value:       "Bye",
+			isHasPrefix: false,
+		},
+	}
 
-	value.NotHasPrefix("Hello")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	for _, tc := range cases {
+		reporter := newMockReporter(t)
 
-	value.NotHasPrefix("hello")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+		if tc.isHasPrefix {
+			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).
+				NotHasPrefix(tc.value).
+				chain.assertNotFailed(t)
+			NewString(reporter, tc.str).
+				HasPrefix(tc.value).
+				chain.assertFailed(t)
+		}
+	}
 }
 
 func TestString_HasSuffix(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value := NewString(reporter, "Hello World")
-
-	value.HasSuffix("World")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.HasSuffix("Hello World")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.HasSuffix("")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.HasPrefix("World!")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.HasSuffix("world")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.HasSuffix("Hello")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.NotHasSuffix("Bye")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	cases := []struct {
+		name        string
+		str         string
+		value       string
+		isHasSuffix bool
+	}{
+		{
+			name:        "has suffix",
+			str:         "Hello World",
+			value:       "World",
+			isHasSuffix: true,
+		},
+		{
+			name:        "full sentence",
+			str:         "Hello World",
+			value:       "Hello World",
+			isHasSuffix: true,
+		},
+		{
+			name:        "empty string",
+			str:         "Hello World",
+			value:       "",
+			isHasSuffix: true,
+		},
+		{
+			name:        "not has suffix because value has extra char",
+			str:         "Hello World",
+			value:       "World!",
+			isHasSuffix: false,
+		},
+		{
+			name:        "not has suffix because case sensitive",
+			str:         "Hello World",
+			value:       "world",
+			isHasSuffix: false,
+		},
+		{
+			name:        "not has suffix because value is prefix",
+			str:         "Hello World",
+			value:       "Hello",
+			isHasSuffix: false,
+		},
+		{
+			name:        "not has suffix because value does not exist in sentence",
+			str:         "Hello World",
+			value:       "Bye",
+			isHasSuffix: false,
+		},
+	}
 
-	value.NotHasSuffix("World")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	for _, tc := range cases {
+		reporter := newMockReporter(t)
 
-	value.NotHasSuffix("world")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+		if tc.isHasSuffix {
+			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).
+				NotHasSuffix(tc.value).
+				chain.assertNotFailed(t)
+			NewString(reporter, tc.str).
+				HasSuffix(tc.value).
+				chain.assertFailed(t)
+		}
+	}
 }
 
 func TestString_HasPrefixFold(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value := NewString(reporter, "Hello World")
-
-	value.HasPrefixFold("hello")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.HasPrefixFold("HeLlO wOrLd")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.HasPrefixFold("")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.HasPrefixFold("World")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.NotHasPrefixFold("Bye")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.NotHasPrefixFold("world")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	cases := []struct {
+		name            string
+		str             string
+		value           string
+		isHasPrefixFold bool
+	}{
+		{
+			name:            "has prefix because case insensitive",
+			str:             "Hello World",
+			value:           "hello",
+			isHasPrefixFold: true,
+		},
+		{
+			name:            "full sentence and case insensitive",
+			str:             "Hello World",
+			value:           "HeLlO wOrLd",
+			isHasPrefixFold: true,
+		},
+		{
+			name:            "empty string",
+			str:             "Hello World",
+			value:           "",
+			isHasPrefixFold: true,
+		},
+		{
+			name:            "not has prefix because value is suffix",
+			str:             "Hello World",
+			value:           "World",
+			isHasPrefixFold: false,
+		},
+		{
+			name:            "not has prefix because value does not exist in sentence",
+			str:             "Hello World",
+			value:           "Bye",
+			isHasPrefixFold: false,
+		},
+		{
+			name:            "not has prefix because value is suffix and case sensitive",
+			str:             "Hello World",
+			value:           "world",
+			isHasPrefixFold: false,
+		},
+		{
+			name:            "not has prefix because value has extra char",
+			str:             "Hello World",
+			value:           "world!",
+			isHasPrefixFold: false,
+		},
+	}
 
-	value.NotHasPrefixFold("world!")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	for _, tc := range cases {
+		reporter := newMockReporter(t)
 
-	value.NotHasPrefixFold("hello")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+		if tc.isHasPrefixFold {
+			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).
+				NotHasPrefixFold(tc.value).
+				chain.assertNotFailed(t)
+			NewString(reporter, tc.str).
+				HasPrefixFold(tc.value).
+				chain.assertFailed(t)
+		}
+	}
 }
 
 func TestString_HasSuffixFold(t *testing.T) {
-	reporter := newMockReporter(t)
-
-	value := NewString(reporter, "Hello World")
-
-	value.HasSuffixFold("world")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.HasSuffixFold("hElLo WoRlD")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.HasSuffixFold("")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
-
-	value.HasSuffixFold("hello")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.HasSuffixFold("world!")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
-
-	value.NotHasSuffixFold("Bye")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+	cases := []struct {
+		name            string
+		str             string
+		value           string
+		isHasSuffixFold bool
+	}{
+		{
+			name:            "has suffix",
+			str:             "Hello World",
+			value:           "world",
+			isHasSuffixFold: true,
+		},
+		{
+			name:            "full sentence and case insensitive",
+			str:             "Hello World",
+			value:           "hElLo WoRlD",
+			isHasSuffixFold: true,
+		},
+		{
+			name:            "empty string",
+			str:             "Hello World",
+			value:           "",
+			isHasSuffixFold: true,
+		},
+		{
+			name:            "not has suffix because value is prefix and case sensitive",
+			str:             "Hello World",
+			value:           "hello",
+			isHasSuffixFold: false,
+		},
+		{
+			name:            "not has suffix because value has extra char",
+			str:             "Hello World",
+			value:           "world!",
+			isHasSuffixFold: false,
+		},
+		{
+			name:            "not has suffix because value does not exist in sentence",
+			str:             "Hello World",
+			value:           "Bye",
+			isHasSuffixFold: false,
+		},
+	}
 
-	value.NotHasSuffixFold("world")
-	value.chain.assertFailed(t)
-	value.chain.clearFailed()
+	for _, tc := range cases {
+		reporter := newMockReporter(t)
 
-	value.NotHasSuffixFold("world!")
-	value.chain.assertNotFailed(t)
-	value.chain.clearFailed()
+		if tc.isHasSuffixFold {
+			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).
+				NotHasSuffixFold(tc.value).
+				chain.assertNotFailed(t)
+			NewString(reporter, tc.str).
+				HasSuffixFold(tc.value).
+				chain.assertFailed(t)
+		}
+	}
 }
 
 func TestString_MatchOne(t *testing.T) {