Skip to content

Commit 4da4c7f

Browse files
committedNov 8, 2023
BeTrueBecause and BeFalseBecause allow for better failure messages
1 parent 6ca6e97 commit 4da4c7f

7 files changed

+156
-47
lines changed
 

‎docs/index.md

+28-2
Original file line numberDiff line numberDiff line change
@@ -768,17 +768,43 @@ succeeds if `ACTUAL` is the zero value for its type *or* if `ACTUAL` is `nil`.
768768
Ω(ACTUAL).Should(BeTrue())
769769
```
770770

771-
succeeds if `ACTUAL` is `bool` typed and has the value `true`. It is an error for `ACTUAL` to not be a `bool`.
771+
succeeds if `ACTUAL` is `bool` typed and has the value `true`. It is an error for `ACTUAL` to not be a `bool`.
772+
773+
Since Gomega has no additional context about your assertion the failure messages are generally not particularly helpful. So it's generally recommended that you use `BeTrueBecause` instead.
772774

773775
> Some matcher libraries have a notion of "truthiness" to assert that an object is present. Gomega is strict, and `BeTrue()` only works with `bool`s. You can use `Ω(ACTUAL).ShouldNot(BeZero())` or `Ω(ACTUAL).ShouldNot(BeNil())` to verify object presence.
774776
777+
### BeTrueBecause(reason)
778+
779+
```go
780+
Ω(ACTUAL).Should(BeTrueBecause(REASON, ARGS...))
781+
```
782+
783+
is just like `BeTrue()` but allows you to pass in a reason. This is a best practice as the default failure message is not particularly helpful. `fmt.Sprintf(REASON, ARGS...)` is used to render the reason. For example:
784+
785+
```go
786+
Ω(cow.JumpedOver(moon)).Should(BeTrueBecause("the cow should have jumped over the moon"))
787+
```
788+
775789
#### BeFalse()
776790

777791
```go
778792
Ω(ACTUAL).Should(BeFalse())
779793
```
780794

781-
succeeds if `ACTUAL` is `bool` typed and has the value `false`. It is an error for `ACTUAL` to not be a `bool`.
795+
succeeds if `ACTUAL` is `bool` typed and has the value `false`. It is an error for `ACTUAL` to not be a `bool`. You should generaly use `BeFalseBecause` instead to pas in a reason for a more helpful error message.
796+
797+
### BeFalseBecause(reason)
798+
799+
```go
800+
Ω(ACTUAL).Should(BeFalseBecause(REASON, ARGS...))
801+
```
802+
803+
is just like `BeFalse()` but allows you to pass in a reason. This is a best practice as the default failure message is not particularly helpful. `fmt.Sprintf(REASON, ARGS...)` is used to render the reason.
804+
805+
```go
806+
Ω(cow.JumpedOver(mars)).Should(BeFalseBecause("the cow should not have jumped over mars"))
807+
```
782808

783809
### Asserting on Errors
784810

‎ghttp/handlers.go

+39-39
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ func NewGHTTPWithGomega(gomega Gomega) *GHTTPWithGomega {
2828
}
2929
}
3030

31-
//CombineHandler takes variadic list of handlers and produces one handler
32-
//that calls each handler in order.
31+
// CombineHandler takes variadic list of handlers and produces one handler
32+
// that calls each handler in order.
3333
func CombineHandlers(handlers ...http.HandlerFunc) http.HandlerFunc {
3434
return func(w http.ResponseWriter, req *http.Request) {
3535
for _, handler := range handlers {
@@ -38,11 +38,11 @@ func CombineHandlers(handlers ...http.HandlerFunc) http.HandlerFunc {
3838
}
3939
}
4040

41-
//VerifyRequest returns a handler that verifies that a request uses the specified method to connect to the specified path
42-
//You may also pass in an optional rawQuery string which is tested against the request's `req.URL.RawQuery`
41+
// VerifyRequest returns a handler that verifies that a request uses the specified method to connect to the specified path
42+
// You may also pass in an optional rawQuery string which is tested against the request's `req.URL.RawQuery`
4343
//
44-
//For path, you may pass in a string, in which case strict equality will be applied
45-
//Alternatively you can pass in a matcher (ContainSubstring("/foo") and MatchRegexp("/foo/[a-f0-9]+") for example)
44+
// For path, you may pass in a string, in which case strict equality will be applied
45+
// Alternatively you can pass in a matcher (ContainSubstring("/foo") and MatchRegexp("/foo/[a-f0-9]+") for example)
4646
func (g GHTTPWithGomega) VerifyRequest(method string, path interface{}, rawQuery ...string) http.HandlerFunc {
4747
return func(w http.ResponseWriter, req *http.Request) {
4848
g.gomega.Expect(req.Method).Should(Equal(method), "Method mismatch")
@@ -61,24 +61,24 @@ func (g GHTTPWithGomega) VerifyRequest(method string, path interface{}, rawQuery
6161
}
6262
}
6363

64-
//VerifyContentType returns a handler that verifies that a request has a Content-Type header set to the
65-
//specified value
64+
// VerifyContentType returns a handler that verifies that a request has a Content-Type header set to the
65+
// specified value
6666
func (g GHTTPWithGomega) VerifyContentType(contentType string) http.HandlerFunc {
6767
return func(w http.ResponseWriter, req *http.Request) {
6868
g.gomega.Expect(req.Header.Get("Content-Type")).Should(Equal(contentType))
6969
}
7070
}
7171

72-
//VerifyMimeType returns a handler that verifies that a request has a specified mime type set
73-
//in Content-Type header
72+
// VerifyMimeType returns a handler that verifies that a request has a specified mime type set
73+
// in Content-Type header
7474
func (g GHTTPWithGomega) VerifyMimeType(mimeType string) http.HandlerFunc {
7575
return func(w http.ResponseWriter, req *http.Request) {
7676
g.gomega.Expect(strings.Split(req.Header.Get("Content-Type"), ";")[0]).Should(Equal(mimeType))
7777
}
7878
}
7979

80-
//VerifyBasicAuth returns a handler that verifies the request contains a BasicAuth Authorization header
81-
//matching the passed in username and password
80+
// VerifyBasicAuth returns a handler that verifies the request contains a BasicAuth Authorization header
81+
// matching the passed in username and password
8282
func (g GHTTPWithGomega) VerifyBasicAuth(username string, password string) http.HandlerFunc {
8383
return func(w http.ResponseWriter, req *http.Request) {
8484
auth := req.Header.Get("Authorization")
@@ -91,11 +91,11 @@ func (g GHTTPWithGomega) VerifyBasicAuth(username string, password string) http.
9191
}
9292
}
9393

94-
//VerifyHeader returns a handler that verifies the request contains the passed in headers.
95-
//The passed in header keys are first canonicalized via http.CanonicalHeaderKey.
94+
// VerifyHeader returns a handler that verifies the request contains the passed in headers.
95+
// The passed in header keys are first canonicalized via http.CanonicalHeaderKey.
9696
//
97-
//The request must contain *all* the passed in headers, but it is allowed to have additional headers
98-
//beyond the passed in set.
97+
// The request must contain *all* the passed in headers, but it is allowed to have additional headers
98+
// beyond the passed in set.
9999
func (g GHTTPWithGomega) VerifyHeader(header http.Header) http.HandlerFunc {
100100
return func(w http.ResponseWriter, req *http.Request) {
101101
for key, values := range header {
@@ -105,9 +105,9 @@ func (g GHTTPWithGomega) VerifyHeader(header http.Header) http.HandlerFunc {
105105
}
106106
}
107107

108-
//VerifyHeaderKV returns a handler that verifies the request contains a header matching the passed in key and values
109-
//(recall that a `http.Header` is a mapping from string (key) to []string (values))
110-
//It is a convenience wrapper around `VerifyHeader` that allows you to avoid having to create an `http.Header` object.
108+
// VerifyHeaderKV returns a handler that verifies the request contains a header matching the passed in key and values
109+
// (recall that a `http.Header` is a mapping from string (key) to []string (values))
110+
// It is a convenience wrapper around `VerifyHeader` that allows you to avoid having to create an `http.Header` object.
111111
func (g GHTTPWithGomega) VerifyHeaderKV(key string, values ...string) http.HandlerFunc {
112112
return g.VerifyHeader(http.Header{key: values})
113113
}
@@ -127,8 +127,8 @@ func (g GHTTPWithGomega) VerifyHost(host interface{}) http.HandlerFunc {
127127
}
128128
}
129129

130-
//VerifyBody returns a handler that verifies that the body of the request matches the passed in byte array.
131-
//It does this using Equal().
130+
// VerifyBody returns a handler that verifies that the body of the request matches the passed in byte array.
131+
// It does this using Equal().
132132
func (g GHTTPWithGomega) VerifyBody(expectedBody []byte) http.HandlerFunc {
133133
return CombineHandlers(
134134
func(w http.ResponseWriter, req *http.Request) {
@@ -140,10 +140,10 @@ func (g GHTTPWithGomega) VerifyBody(expectedBody []byte) http.HandlerFunc {
140140
)
141141
}
142142

143-
//VerifyJSON returns a handler that verifies that the body of the request is a valid JSON representation
144-
//matching the passed in JSON string. It does this using Gomega's MatchJSON method
143+
// VerifyJSON returns a handler that verifies that the body of the request is a valid JSON representation
144+
// matching the passed in JSON string. It does this using Gomega's MatchJSON method
145145
//
146-
//VerifyJSON also verifies that the request's content type is application/json
146+
// VerifyJSON also verifies that the request's content type is application/json
147147
func (g GHTTPWithGomega) VerifyJSON(expectedJSON string) http.HandlerFunc {
148148
return CombineHandlers(
149149
g.VerifyMimeType("application/json"),
@@ -156,9 +156,9 @@ func (g GHTTPWithGomega) VerifyJSON(expectedJSON string) http.HandlerFunc {
156156
)
157157
}
158158

159-
//VerifyJSONRepresenting is similar to VerifyJSON. Instead of taking a JSON string, however, it
160-
//takes an arbitrary JSON-encodable object and verifies that the requests's body is a JSON representation
161-
//that matches the object
159+
// VerifyJSONRepresenting is similar to VerifyJSON. Instead of taking a JSON string, however, it
160+
// takes an arbitrary JSON-encodable object and verifies that the requests's body is a JSON representation
161+
// that matches the object
162162
func (g GHTTPWithGomega) VerifyJSONRepresenting(object interface{}) http.HandlerFunc {
163163
data, err := json.Marshal(object)
164164
g.gomega.Expect(err).ShouldNot(HaveOccurred())
@@ -168,10 +168,10 @@ func (g GHTTPWithGomega) VerifyJSONRepresenting(object interface{}) http.Handler
168168
)
169169
}
170170

171-
//VerifyForm returns a handler that verifies a request contains the specified form values.
171+
// VerifyForm returns a handler that verifies a request contains the specified form values.
172172
//
173-
//The request must contain *all* of the specified values, but it is allowed to have additional
174-
//form values beyond the passed in set.
173+
// The request must contain *all* of the specified values, but it is allowed to have additional
174+
// form values beyond the passed in set.
175175
func (g GHTTPWithGomega) VerifyForm(values url.Values) http.HandlerFunc {
176176
return func(w http.ResponseWriter, r *http.Request) {
177177
err := r.ParseForm()
@@ -182,17 +182,17 @@ func (g GHTTPWithGomega) VerifyForm(values url.Values) http.HandlerFunc {
182182
}
183183
}
184184

185-
//VerifyFormKV returns a handler that verifies a request contains a form key with the specified values.
185+
// VerifyFormKV returns a handler that verifies a request contains a form key with the specified values.
186186
//
187-
//It is a convenience wrapper around `VerifyForm` that lets you avoid having to create a `url.Values` object.
187+
// It is a convenience wrapper around `VerifyForm` that lets you avoid having to create a `url.Values` object.
188188
func (g GHTTPWithGomega) VerifyFormKV(key string, values ...string) http.HandlerFunc {
189189
return g.VerifyForm(url.Values{key: values})
190190
}
191191

192-
//VerifyProtoRepresenting returns a handler that verifies that the body of the request is a valid protobuf
193-
//representation of the passed message.
192+
// VerifyProtoRepresenting returns a handler that verifies that the body of the request is a valid protobuf
193+
// representation of the passed message.
194194
//
195-
//VerifyProtoRepresenting also verifies that the request's content type is application/x-protobuf
195+
// VerifyProtoRepresenting also verifies that the request's content type is application/x-protobuf
196196
func (g GHTTPWithGomega) VerifyProtoRepresenting(expected proto.Message) http.HandlerFunc {
197197
return CombineHandlers(
198198
g.VerifyContentType("application/x-protobuf"),
@@ -205,7 +205,7 @@ func (g GHTTPWithGomega) VerifyProtoRepresenting(expected proto.Message) http.Ha
205205
actualValuePtr := reflect.New(expectedType.Elem())
206206

207207
actual, ok := actualValuePtr.Interface().(proto.Message)
208-
g.gomega.Expect(ok).Should(BeTrue(), "Message value is not a proto.Message")
208+
g.gomega.Expect(ok).Should(BeTrueBecause("Message value should be a proto.Message"))
209209

210210
err = proto.Unmarshal(body, actual)
211211
g.gomega.Expect(err).ShouldNot(HaveOccurred(), "Failed to unmarshal protobuf")
@@ -324,10 +324,10 @@ func (g GHTTPWithGomega) RespondWithJSONEncodedPtr(statusCode *int, object inter
324324
}
325325
}
326326

327-
//RespondWithProto returns a handler that responds to a request with the specified status code and a body
328-
//containing the protobuf serialization of the provided message.
327+
// RespondWithProto returns a handler that responds to a request with the specified status code and a body
328+
// containing the protobuf serialization of the provided message.
329329
//
330-
//Also, RespondWithProto can be given an optional http.Header. The headers defined therein will be added to the response headers.
330+
// Also, RespondWithProto can be given an optional http.Header. The headers defined therein will be added to the response headers.
331331
func (g GHTTPWithGomega) RespondWithProto(statusCode int, message proto.Message, optionalHeader ...http.Header) http.HandlerFunc {
332332
return func(w http.ResponseWriter, req *http.Request) {
333333
data, err := proto.Marshal(message)

‎matchers.go

+17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package gomega
22

33
import (
4+
"fmt"
45
"time"
56

67
"github.com/google/go-cmp/cmp"
@@ -52,15 +53,31 @@ func BeNil() types.GomegaMatcher {
5253
}
5354

5455
// BeTrue succeeds if actual is true
56+
//
57+
// In general, it's better to use `BeTrueBecause(reason)` to provide a more useful error message if a true check fails.
5558
func BeTrue() types.GomegaMatcher {
5659
return &matchers.BeTrueMatcher{}
5760
}
5861

5962
// BeFalse succeeds if actual is false
63+
//
64+
// In general, it's better to use `BeFalseBecause(reason)` to provide a more useful error message if a false check fails.
6065
func BeFalse() types.GomegaMatcher {
6166
return &matchers.BeFalseMatcher{}
6267
}
6368

69+
// BeTrueBecause succeeds if actual is true and displays the provided reason if it is false
70+
// fmt.Sprintf is used to render the reason
71+
func BeTrueBecause(format string, args ...any) types.GomegaMatcher {
72+
return &matchers.BeTrueMatcher{Reason: fmt.Sprintf(format, args...)}
73+
}
74+
75+
// BeFalseBecause succeeds if actual is false and displays the provided reason if it is true.
76+
// fmt.Sprintf is used to render the reason
77+
func BeFalseBecause(format string, args ...any) types.GomegaMatcher {
78+
return &matchers.BeFalseMatcher{Reason: fmt.Sprintf(format, args...)}
79+
}
80+
6481
// HaveOccurred succeeds if actual is a non-nil error
6582
// The typical Go error checking pattern looks like:
6683
//

‎matchers/be_false_matcher.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
)
1010

1111
type BeFalseMatcher struct {
12+
Reason string
1213
}
1314

1415
func (matcher *BeFalseMatcher) Match(actual interface{}) (success bool, err error) {
@@ -20,9 +21,17 @@ func (matcher *BeFalseMatcher) Match(actual interface{}) (success bool, err erro
2021
}
2122

2223
func (matcher *BeFalseMatcher) FailureMessage(actual interface{}) (message string) {
23-
return format.Message(actual, "to be false")
24+
if matcher.Reason == "" {
25+
return format.Message(actual, "to be false")
26+
} else {
27+
return matcher.Reason
28+
}
2429
}
2530

2631
func (matcher *BeFalseMatcher) NegatedFailureMessage(actual interface{}) (message string) {
27-
return format.Message(actual, "not to be false")
32+
if matcher.Reason == "" {
33+
return format.Message(actual, "not to be false")
34+
} else {
35+
return fmt.Sprintf(`Expected not false but got false\nNegation of "%s" failed`, matcher.Reason)
36+
}
2837
}

‎matchers/be_false_matcher_test.go

+25-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
. "github.com/onsi/gomega/matchers"
77
)
88

9-
var _ = Describe("BeFalse", func() {
9+
var _ = Describe("BeFalse and BeFalseBecause", func() {
1010
It("should handle true and false correctly", func() {
1111
Expect(true).ShouldNot(BeFalse())
1212
Expect(false).Should(BeFalse())
@@ -17,4 +17,28 @@ var _ = Describe("BeFalse", func() {
1717
Expect(success).Should(BeFalse())
1818
Expect(err).Should(HaveOccurred())
1919
})
20+
21+
It("returns the passed in failure message if provided", func() {
22+
x := 100
23+
err := InterceptGomegaFailure(func() { Expect(x == 100).Should(BeFalse()) })
24+
Ω(err.Error()).Should(Equal("Expected\n <bool>: true\nto be false"))
25+
26+
err = InterceptGomegaFailure(func() { Expect(x == 100).Should(BeFalseBecause("x should not be 100%%")) })
27+
Ω(err.Error()).Should(Equal("x should not be 100%"))
28+
29+
err = InterceptGomegaFailure(func() { Expect(x == 100).Should(BeFalseBecause("x should not be %d%%", 100)) })
30+
Ω(err.Error()).Should(Equal("x should not be 100%"))
31+
})
32+
33+
It("prints out a useful message if a negation fails", func() {
34+
x := 10
35+
err := InterceptGomegaFailure(func() { Expect(x == 100).ShouldNot(BeFalse()) })
36+
Ω(err.Error()).Should(Equal("Expected\n <bool>: false\nnot to be false"))
37+
38+
err = InterceptGomegaFailure(func() { Expect(x == 100).ShouldNot(BeFalseBecause("x should not be 100%%")) })
39+
Ω(err.Error()).Should(Equal(`Expected not false but got false\nNegation of "x should not be 100%" failed`))
40+
41+
err = InterceptGomegaFailure(func() { Expect(x == 100).ShouldNot(BeFalseBecause("x should not be %d%%", 100)) })
42+
Ω(err.Error()).Should(Equal(`Expected not false but got false\nNegation of "x should not be 100%" failed`))
43+
})
2044
})

‎matchers/be_true_matcher.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
)
1010

1111
type BeTrueMatcher struct {
12+
Reason string
1213
}
1314

1415
func (matcher *BeTrueMatcher) Match(actual interface{}) (success bool, err error) {
@@ -20,9 +21,17 @@ func (matcher *BeTrueMatcher) Match(actual interface{}) (success bool, err error
2021
}
2122

2223
func (matcher *BeTrueMatcher) FailureMessage(actual interface{}) (message string) {
23-
return format.Message(actual, "to be true")
24+
if matcher.Reason == "" {
25+
return format.Message(actual, "to be true")
26+
} else {
27+
return matcher.Reason
28+
}
2429
}
2530

2631
func (matcher *BeTrueMatcher) NegatedFailureMessage(actual interface{}) (message string) {
27-
return format.Message(actual, "not to be true")
32+
if matcher.Reason == "" {
33+
return format.Message(actual, "not to be true")
34+
} else {
35+
return fmt.Sprintf(`Expected not true but got true\nNegation of "%s" failed`, matcher.Reason)
36+
}
2837
}

‎matchers/be_true_matcher_test.go

+25-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
. "github.com/onsi/gomega/matchers"
77
)
88

9-
var _ = Describe("BeTrue", func() {
9+
var _ = Describe("BeTrue and BeTrueBecause", func() {
1010
It("should handle true and false correctly", func() {
1111
Expect(true).Should(BeTrue())
1212
Expect(false).ShouldNot(BeTrue())
@@ -17,4 +17,28 @@ var _ = Describe("BeTrue", func() {
1717
Expect(success).Should(BeFalse())
1818
Expect(err).Should(HaveOccurred())
1919
})
20+
21+
It("returns the passed in failure message if provided", func() {
22+
x := 10
23+
err := InterceptGomegaFailure(func() { Expect(x == 100).Should(BeTrue()) })
24+
Ω(err.Error()).Should(Equal("Expected\n <bool>: false\nto be true"))
25+
26+
err = InterceptGomegaFailure(func() { Expect(x == 100).Should(BeTrueBecause("x should be 100%%")) })
27+
Ω(err.Error()).Should(Equal("x should be 100%"))
28+
29+
err = InterceptGomegaFailure(func() { Expect(x == 100).Should(BeTrueBecause("x should be %d%%", 100)) })
30+
Ω(err.Error()).Should(Equal("x should be 100%"))
31+
})
32+
33+
It("prints out a useful message if a negation fails", func() {
34+
x := 100
35+
err := InterceptGomegaFailure(func() { Expect(x == 100).ShouldNot(BeTrue()) })
36+
Ω(err.Error()).Should(Equal("Expected\n <bool>: true\nnot to be true"))
37+
38+
err = InterceptGomegaFailure(func() { Expect(x == 100).ShouldNot(BeTrueBecause("x should be 100%%")) })
39+
Ω(err.Error()).Should(Equal(`Expected not true but got true\nNegation of "x should be 100%" failed`))
40+
41+
err = InterceptGomegaFailure(func() { Expect(x == 100).ShouldNot(BeTrueBecause("x should be %d%%", 100)) })
42+
Ω(err.Error()).Should(Equal(`Expected not true but got true\nNegation of "x should be 100%" failed`))
43+
})
2044
})

0 commit comments

Comments
 (0)
Please sign in to comment.