...

Source file src/github.com/onsi/gomega/gcustom/make_matcher_test.go

Documentation: github.com/onsi/gomega/gcustom

     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  })
   268  

View as plain text