1 package matchers_test
2
3 import (
4 "fmt"
5 "time"
6
7 . "github.com/onsi/ginkgo/v2"
8 . "github.com/onsi/gomega"
9 )
10
11 type Book struct {
12 Title string
13 Author person
14 Pages int
15 Sequel *Book
16 Prequel *Book
17 }
18
19 func (book Book) AuthorName() string {
20 return fmt.Sprintf("%s %s", book.Author.FirstName, book.Author.LastName)
21 }
22
23 func (book Book) AbbreviatedAuthor() person {
24 return person{
25 FirstName: book.Author.FirstName[0:3],
26 LastName: book.Author.LastName[0:3],
27 DOB: book.Author.DOB,
28 }
29 }
30
31 func (book Book) ReceiverTitle() string {
32 return book.Title
33 }
34
35 func (book *Book) PointerReceiverTitle() string {
36 return book.Title
37 }
38
39 func (book Book) NoReturn() {
40 }
41
42 func (book Book) TooManyReturn() (string, error) {
43 return "", nil
44 }
45
46 func (book Book) HasArg(arg string) string {
47 return arg
48 }
49
50 type person struct {
51 FirstName string
52 LastName string
53 DOB time.Time
54 }
55
56 var _ = Describe("HaveField", func() {
57 var book Book
58 BeforeEach(func() {
59 book = Book{
60 Title: "Les Miserables",
61 Author: person{
62 FirstName: "Victor",
63 LastName: "Hugo",
64 DOB: time.Date(1802, 2, 26, 0, 0, 0, 0, time.UTC),
65 },
66 Pages: 2783,
67 Sequel: &Book{
68 Title: "Les Miserables 2",
69 },
70 }
71 })
72
73 DescribeTable("traversing the struct works",
74 func(field string, expected interface{}) {
75 Ω(book).Should(HaveField(field, expected))
76 },
77 Entry("Top-level field with default submatcher", "Title", "Les Miserables"),
78 Entry("Top-level field with custom submatcher", "Title", ContainSubstring("Les Mis")),
79 Entry("Nested field", "Author.FirstName", "Victor"),
80 Entry("Top-level method", "AuthorName()", "Victor Hugo"),
81 Entry("Nested method", "Author.DOB.Year()", BeNumerically("<", 1900)),
82 Entry("Traversing past a method", "AbbreviatedAuthor().FirstName", Equal("Vic")),
83 Entry("Traversing a pointer", "Sequel.Title", "Les Miserables 2"),
84 )
85
86 DescribeTable("negation works",
87 func(field string, expected interface{}) {
88 Ω(book).ShouldNot(HaveField(field, expected))
89 },
90 Entry("Top-level field with default submatcher", "Title", "Les Mis"),
91 Entry("Top-level field with custom submatcher", "Title", ContainSubstring("Notre Dame")),
92 Entry("Nested field", "Author.FirstName", "Hugo"),
93 Entry("Top-level method", "AuthorName()", "Victor M. Hugo"),
94 Entry("Nested method", "Author.DOB.Year()", BeNumerically(">", 1900)),
95 Entry("Traversing a pointer", "Sequel.Title", "Les Mis 2"),
96 )
97
98 Describe("when field lookup fails", func() {
99 It("errors appropriately", func() {
100 success, err := HaveField("BookName", "Les Miserables").Match(book)
101 Ω(success).Should(BeFalse())
102 Ω(err.Error()).Should(ContainSubstring("HaveField could not find field named '%s' in struct:", "BookName"))
103
104 success, err = HaveField("BookName", "Les Miserables").Match(book)
105 Ω(success).Should(BeFalse())
106 Ω(err.Error()).Should(ContainSubstring("HaveField could not find field named '%s' in struct:", "BookName"))
107
108 success, err = HaveField("AuthorName", "Victor Hugo").Match(book)
109 Ω(success).Should(BeFalse())
110 Ω(err.Error()).Should(ContainSubstring("HaveField could not find field named '%s' in struct:", "AuthorName"))
111
112 success, err = HaveField("Title()", "Les Miserables").Match(book)
113 Ω(success).Should(BeFalse())
114 Ω(err.Error()).Should(ContainSubstring("HaveField could not find method named '%s' in struct of type matchers_test.Book.", "Title()"))
115
116 success, err = HaveField("NoReturn()", "Les Miserables").Match(book)
117 Ω(success).Should(BeFalse())
118 Ω(err.Error()).Should(ContainSubstring("HaveField found an invalid method named 'NoReturn()' in struct of type matchers_test.Book.\nMethods must take no arguments and return exactly one value."))
119
120 success, err = HaveField("TooManyReturn()", "Les Miserables").Match(book)
121 Ω(success).Should(BeFalse())
122 Ω(err.Error()).Should(ContainSubstring("HaveField found an invalid method named 'TooManyReturn()' in struct of type matchers_test.Book.\nMethods must take no arguments and return exactly one value."))
123
124 success, err = HaveField("HasArg()", "Les Miserables").Match(book)
125 Ω(success).Should(BeFalse())
126 Ω(err.Error()).Should(ContainSubstring("HaveField found an invalid method named 'HasArg()' in struct of type matchers_test.Book.\nMethods must take no arguments and return exactly one value."))
127
128 success, err = HaveField("Pages.Count", 2783).Match(book)
129 Ω(success).Should(BeFalse())
130 Ω(err.Error()).Should(Equal("HaveField encountered:\n <int>: 2783\nWhich is not a struct."))
131
132 success, err = HaveField("Author.Abbreviation", "Vic").Match(book)
133 Ω(success).Should(BeFalse())
134 Ω(err.Error()).Should(ContainSubstring("HaveField could not find field named '%s' in struct:", "Abbreviation"))
135
136 success, err = HaveField("Prequel.Title", "Les Miserables 0").Match(book)
137 Ω(success).Should(BeFalse())
138 Ω(err.Error()).Should(ContainSubstring("HaveField encountered nil while dereferencing a pointer of type *matchers_test.Book."))
139 })
140 })
141
142 Describe("Failure Messages", func() {
143 It("renders the underlying matcher failure", func() {
144 matcher := HaveField("Title", "Les Mis")
145 success, err := matcher.Match(book)
146 Ω(success).Should(BeFalse())
147 Ω(err).ShouldNot(HaveOccurred())
148
149 msg := matcher.FailureMessage(book)
150 Ω(msg).Should(Equal("Value for field 'Title' failed to satisfy matcher.\nExpected\n <string>: Les Miserables\nto equal\n <string>: Les Mis"))
151
152 matcher = HaveField("Title", "Les Miserables")
153 success, err = matcher.Match(book)
154 Ω(success).Should(BeTrue())
155 Ω(err).ShouldNot(HaveOccurred())
156
157 msg = matcher.NegatedFailureMessage(book)
158 Ω(msg).Should(Equal("Value for field 'Title' satisfied matcher, but should not have.\nExpected\n <string>: Les Miserables\nnot to equal\n <string>: Les Miserables"))
159 })
160 })
161
162 Describe("receiver lookup", func() {
163 DescribeTable("(pointer) receiver lookup works",
164 func(field string, expected interface{}) {
165 Ω(&book).Should(HaveField(field, expected))
166 },
167 Entry("non-pointer receiver", "ReceiverTitle()", "Les Miserables"),
168 Entry("pointer receiver", "PointerReceiverTitle()", "Les Miserables"),
169 )
170
171 It("correctly fails", func() {
172 matcher := HaveField("ReceiverTitle()", "Les Miserables")
173 answer := struct{}{}
174 Ω(matcher.Match(answer)).Error().Should(MatchError(
175 "HaveField could not find method named 'ReceiverTitle()' in struct of type struct {}."))
176
177 matcher = HaveField("PointerReceiverTitle()", "Les Miserables")
178 Ω(matcher.Match(book)).Error().Should(MatchError(
179 "HaveField could not find method named 'PointerReceiverTitle()' in struct of type matchers_test.Book."))
180 })
181 })
182
183 })
184
View as plain text