github.com/onsi/gomega@v1.32.0/gcustom/make_matcher_test.go (about) 1 package gcustom_test 2 3 import ( 4 "errors" 5 "runtime" 6 "strings" 7 8 . "github.com/onsi/ginkgo/v2" 9 . "github.com/onsi/gomega" 10 "github.com/onsi/gomega/gcustom" 11 "github.com/onsi/gomega/internal" 12 ) 13 14 // InstrumentedGomega 15 type InstrumentedGomega struct { 16 G *internal.Gomega 17 FailureMessage string 18 FailureSkip []int 19 RegisteredHelpers []string 20 } 21 22 func NewInstrumentedGomega() *InstrumentedGomega { 23 out := &InstrumentedGomega{} 24 25 out.G = internal.NewGomega(internal.FetchDefaultDurationBundle()) 26 out.G.Fail = func(message string, skip ...int) { 27 out.FailureMessage = message 28 out.FailureSkip = skip 29 } 30 out.G.THelper = func() { 31 pc, _, _, _ := runtime.Caller(1) 32 f := runtime.FuncForPC(pc) 33 funcName := strings.TrimPrefix(f.Name(), "github.com/onsi/gomega/internal.") 34 out.RegisteredHelpers = append(out.RegisteredHelpers, funcName) 35 } 36 37 return out 38 } 39 40 type someType struct { 41 Name string 42 } 43 44 var _ = Describe("MakeMatcher", func() { 45 It("generatees a custom matcher that satisfies the GomegaMatcher interface and renders correct failure messages", func() { 46 m := gcustom.MakeMatcher(func(a int) (bool, error) { 47 if a == 0 { 48 return true, nil 49 } 50 if a == 1 { 51 return false, nil 52 } 53 return false, errors.New("bam") 54 }).WithMessage("match") 55 56 Ω(0).Should(m) 57 Ω(1).ShouldNot(m) 58 59 ig := NewInstrumentedGomega() 60 ig.G.Ω(1).Should(m) 61 Ω(ig.FailureMessage).Should(Equal("Expected:\n <int>: 1\nto match")) 62 63 ig.G.Ω(0).ShouldNot(m) 64 Ω(ig.FailureMessage).Should(Equal("Expected:\n <int>: 0\nnot to match")) 65 66 ig.G.Ω(2).Should(m) 67 Ω(ig.FailureMessage).Should(Equal("bam")) 68 69 ig.G.Ω(2).ShouldNot(m) 70 Ω(ig.FailureMessage).Should(Equal("bam")) 71 }) 72 73 Describe("validating and wrapping the MatchFunc", func() { 74 DescribeTable("it panics when passed an invalid function", func(f any) { 75 Expect(func() { 76 gcustom.MakeMatcher(f) 77 }).To(PanicWith("MakeMatcher must be passed a function that takes one argument and returns (bool, error)")) 78 }, 79 Entry("a non-function", "foo"), 80 Entry("a non-function", 1), 81 Entry("a function with no input", func() (bool, error) { return false, nil }), 82 Entry("a function with too many inputs", func(a int, b string) (bool, error) { return false, nil }), 83 Entry("a function with no outputs", func(a any) {}), 84 Entry("a function with insufficient outputs", func(a any) bool { return false }), 85 Entry("a function with insufficient outputs", func(a any) error { return nil }), 86 Entry("a function with too many outputs", func(a any) (bool, error, string) { return false, nil, "" }), 87 Entry("a function with the wrong types of outputs", func(a any) (int, error) { return 1, nil }), 88 Entry("a function with the wrong types of outputs", func(a any) (bool, int) { return false, 1 }), 89 ) 90 91 Context("when the match func accepts any actual", func() { 92 It("always passes in the actual, regardless of type", func() { 93 var passedIn any 94 m := gcustom.MakeMatcher(func(a any) (bool, error) { 95 passedIn = a 96 return true, nil 97 }) 98 99 m.Match(1) 100 Ω(passedIn).Should(Equal(1)) 101 102 m.Match("foo") 103 Ω(passedIn).Should(Equal("foo")) 104 105 m.Match(someType{"foo"}) 106 Ω(passedIn).Should(Equal(someType{"foo"})) 107 108 c := make(chan bool) 109 m.Match(c) 110 Ω(passedIn).Should(Equal(c)) 111 }) 112 }) 113 114 Context("when the match func accepts a specific type", func() { 115 It("ensure the type matches before calling func", func() { 116 var passedIn any 117 m := gcustom.MakeMatcher(func(a int) (bool, error) { 118 passedIn = a 119 return true, nil 120 }) 121 122 success, err := m.Match(1) 123 Ω(success).Should(BeTrue()) 124 Ω(err).ShouldNot(HaveOccurred()) 125 Ω(passedIn).Should(Equal(1)) 126 127 passedIn = nil 128 success, err = m.Match(1.2) 129 Ω(success).Should(BeFalse()) 130 Ω(err).Should(MatchError(ContainSubstring("Matcher expected actual of type <int>. Got:\n <float64>: 1.2"))) 131 Ω(passedIn).Should(BeNil()) 132 133 m = gcustom.MakeMatcher(func(a someType) (bool, error) { 134 passedIn = a 135 return true, nil 136 }) 137 138 success, err = m.Match(someType{"foo"}) 139 Ω(success).Should(BeTrue()) 140 Ω(err).ShouldNot(HaveOccurred()) 141 Ω(passedIn).Should(Equal(someType{"foo"})) 142 143 passedIn = nil 144 success, err = m.Match("foo") 145 Ω(success).Should(BeFalse()) 146 Ω(err).Should(MatchError(ContainSubstring("Matcher expected actual of type <gcustom_test.someType>. Got:\n <string>: foo"))) 147 Ω(passedIn).Should(BeNil()) 148 149 }) 150 }) 151 152 Context("when the match func accepts a nil-able type", func() { 153 It("ensure nil matches the type", func() { 154 var passedIn any 155 m := gcustom.MakeMatcher(func(a *someType) (bool, error) { 156 passedIn = a 157 return true, nil 158 }) 159 160 success, err := m.Match(nil) 161 Ω(success).Should(BeTrue()) 162 Ω(err).ShouldNot(HaveOccurred()) 163 Ω(passedIn).Should(BeNil()) 164 }) 165 }) 166 }) 167 168 It("calls the matchFunc and returns whatever it returns when Match is called", func() { 169 m := gcustom.MakeMatcher(func(a int) (bool, error) { 170 if a == 0 { 171 return true, nil 172 } 173 if a == 1 { 174 return false, nil 175 } 176 return false, errors.New("bam") 177 }) 178 179 Ω(m.Match(0)).Should(BeTrue()) 180 Ω(m.Match(1)).Should(BeFalse()) 181 success, err := m.Match(2) 182 Ω(success).Should(BeFalse()) 183 Ω(err).Should(MatchError("bam")) 184 }) 185 186 Describe("rendering messages", func() { 187 var m gcustom.CustomGomegaMatcher 188 BeforeEach(func() { 189 m = gcustom.MakeMatcher(func(a any) (bool, error) { return false, nil }) 190 }) 191 192 Context("when no message is configured", func() { 193 It("renders a simple canned message", func() { 194 Ω(m.FailureMessage(3)).Should(Equal("Custom matcher failed for:\n <int>: 3")) 195 Ω(m.NegatedFailureMessage(3)).Should(Equal("Custom matcher succeeded (but was expected to fail) for:\n <int>: 3")) 196 }) 197 }) 198 199 Context("when a simple message is configured", func() { 200 It("tacks that message onto the end of a formatted string", func() { 201 m = m.WithMessage("have been confabulated") 202 Ω(m.FailureMessage(3)).Should(Equal("Expected:\n <int>: 3\nto have been confabulated")) 203 Ω(m.NegatedFailureMessage(3)).Should(Equal("Expected:\n <int>: 3\nnot to have been confabulated")) 204 205 m = gcustom.MakeMatcher(func(a any) (bool, error) { return false, nil }, "have been confabulated") 206 Ω(m.FailureMessage(3)).Should(Equal("Expected:\n <int>: 3\nto have been confabulated")) 207 Ω(m.NegatedFailureMessage(3)).Should(Equal("Expected:\n <int>: 3\nnot to have been confabulated")) 208 209 }) 210 }) 211 212 Context("when a template is registered", func() { 213 It("uses that template", func() { 214 m = m.WithTemplate("{{.Failure}} {{.NegatedFailure}} {{.To}} {{.FormattedActual}} {{.Actual.Name}}") 215 Ω(m.FailureMessage(someType{"foo"})).Should(Equal("true false to <gcustom_test.someType>: {Name: \"foo\"} foo")) 216 Ω(m.NegatedFailureMessage(someType{"foo"})).Should(Equal("false true not to <gcustom_test.someType>: {Name: \"foo\"} foo")) 217 218 }) 219 }) 220 221 Context("when a template with custom data is registered", func() { 222 It("provides that custom data", func() { 223 m = m.WithTemplate("{{.Failure}} {{.NegatedFailure}} {{.To}} {{.FormattedActual}} {{.Actual.Name}} {{.Data}}", 17) 224 225 Ω(m.FailureMessage(someType{"foo"})).Should(Equal("true false to <gcustom_test.someType>: {Name: \"foo\"} foo 17")) 226 Ω(m.NegatedFailureMessage(someType{"foo"})).Should(Equal("false true not to <gcustom_test.someType>: {Name: \"foo\"} foo 17")) 227 }) 228 229 It("provides a mechanism for formatting custom data", func() { 230 m = m.WithTemplate("{{format .Data}}", 17) 231 232 Ω(m.FailureMessage(0)).Should(Equal("<int>: 17")) 233 Ω(m.NegatedFailureMessage(0)).Should(Equal("<int>: 17")) 234 235 m = m.WithTemplate("{{format .Data 1}}", 17) 236 237 Ω(m.FailureMessage(0)).Should(Equal(" <int>: 17")) 238 Ω(m.NegatedFailureMessage(0)).Should(Equal(" <int>: 17")) 239 240 }) 241 }) 242 243 Context("when a precompiled template is registered", func() { 244 It("uses that template", func() { 245 templ, err := gcustom.ParseTemplate("{{.Failure}} {{.NegatedFailure}} {{.To}} {{.FormattedActual}} {{.Actual.Name}} {{format .Data}}") 246 Ω(err).ShouldNot(HaveOccurred()) 247 248 m = m.WithPrecompiledTemplate(templ, 17) 249 Ω(m.FailureMessage(someType{"foo"})).Should(Equal("true false to <gcustom_test.someType>: {Name: \"foo\"} foo <int>: 17")) 250 Ω(m.NegatedFailureMessage(someType{"foo"})).Should(Equal("false true not to <gcustom_test.someType>: {Name: \"foo\"} foo <int>: 17")) 251 }) 252 253 It("can also take a template as an argument upon construction", func() { 254 templ, err := gcustom.ParseTemplate("{{.To}} {{format .Data}}") 255 Ω(err).ShouldNot(HaveOccurred()) 256 m = gcustom.MakeMatcher(func(a any) (bool, error) { return false, nil }, templ) 257 258 Ω(m.FailureMessage(0)).Should(Equal("to <nil>: nil")) 259 Ω(m.NegatedFailureMessage(0)).Should(Equal("not to <nil>: nil")) 260 261 m = m.WithTemplateData(17) 262 Ω(m.FailureMessage(0)).Should(Equal("to <int>: 17")) 263 Ω(m.NegatedFailureMessage(0)).Should(Equal("not to <int>: 17")) 264 }) 265 }) 266 }) 267 })