Skip to content

Commit 6015576

Browse files
committed
finish documenting gcustom
1 parent 0cfc53b commit 6015576

File tree

1 file changed

+46
-4
lines changed

1 file changed

+46
-4
lines changed

docs/index.md

+46-4
Original file line numberDiff line numberDiff line change
@@ -1617,11 +1617,11 @@ type GomegaMatcher interface {
16171617
}
16181618
```
16191619

1620-
For the simplest cases, new matchers can be [created by composition](#composing-matchers). In addition to this chapter, please take a look at the [Building Custom Matchers](https://onsi.github.io/ginkgo/#building-custom-matchers) section of the Ginkgo and Gomega patterns chapter in the Ginkgo docs. Gomega's building blocks have evolved since the Gomega docs were written and while this section remains valid - the [Building Custom Matchers](https://onsi.github.io/ginkgo/#building-custom-matchers) docs present a modern way to more quickly construct custom matchers.
1620+
For the simplest cases, new matchers can be [created by composition](#composing-matchers). Please also take a look at the [Building Custom Matchers](https://onsi.github.io/ginkgo/#building-custom-matchers) section of the Ginkgo and Gomega patterns chapter in the Ginkgo docs for additional examples.
16211621

1622-
But writing domain-specific custom matchers is also trivial and highly encouraged. Let's work through an example.
1622+
In addition to composition, however, it is fairly straightforward to build domain-specific custom matchers. You can create new types that satisfy the `GomegaMatcher` interace *or* you can use the `gcustom` package to build matchers out of simple functions.
16231623

1624-
> The `GomegaMatcher` interface is defined in the `types` subpackage.
1624+
Let's work through an example and illustrate both approaches.
16251625

16261626
### A Custom Matcher: RepresentJSONifiedObject(EXPECTED interface{})
16271627

@@ -1689,7 +1689,49 @@ Let's break this down:
16891689
- It is guaranteed that `FailureMessage` and `NegatedFailureMessage` will only be called *after* `Match`, so you can save off any state you need to compute the messages in `Match`.
16901690
- Finally, it is common for matchers to make extensive use of the `reflect` library to interpret the generic inputs they receive. In this case, the `representJSONMatcher` goes through some `reflect` gymnastics to create a pointer to a new object with the same type as the `expected` object, read and decode JSON from `actual` into that pointer, and then deference the pointer and compare the result to the `expected` object.
16911691

1692-
You might test drive this matcher while writing it using Ginkgo. Your test might look like:
1692+
### gcustom: A convenient mechanism for buildling custom matchers
1693+
1694+
[`gcustom`](https://github.com/onsi/gomega/tree/master/gcustom) is a package that makes building custom matchers easy. Rather than define new types, you can simply provide `gcustom.MakeMatcher` with a function. The [godocs](https://pkg.go.dev/github.com/onsi/gomega/gcustom) for `gcustom` have all the details but here's how `RepresentJSONifiedObject` could be implemented with `gcustom`:
1695+
1696+
1697+
```go
1698+
package json_response_matcher
1699+
1700+
import (
1701+
"github.com/onsi/gomega/types"
1702+
"github.com/onsi/gomega/gcustom"
1703+
1704+
"encoding/json"
1705+
"fmt"
1706+
"net/http"
1707+
"reflect"
1708+
)
1709+
1710+
func RepresentJSONifiedObject(expected interface{}) types.GomegaMatcher {
1711+
return gcustom.MakeMatcher(func(response *http.Response) (bool, err) {
1712+
pointerToObjectOfExpectedType := reflect.New(reflect.TypeOf(matcher.expected)).Interface()
1713+
err = json.NewDecoder(response.Body).Decode(pointerToObjectOfExpectedType)
1714+
if err != nil {
1715+
return false, fmt.Errorf("Failed to decode JSON: %w", err.Error())
1716+
}
1717+
1718+
decodedObject := reflect.ValueOf(pointerToObjectOfExpectedType).Elem().Interface()
1719+
return reflect.DeepEqual(decodedObject, matcher.expected), nil
1720+
}).WithTemplate("Expected:\n{{.FormattedActual}}\n{{.To}} contain the JSON representation of\n{{format .Data 1}}").WithTemplateData(expected)
1721+
}
1722+
```
1723+
1724+
The [`gcustom` godocs](https://pkg.go.dev/github.com/onsi/gomega/gcustom) go into much more detail but we can point out a few of the convenient features of `gcustom` here:
1725+
1726+
- `gcustom` can take a matcher function that accepts a concrete type. In our case `func(response *https.Response) (bool, err)` - when this is done, the matcher built by `gcustom` takes care of all the type-checking for you and will only call your match function if an object of the correct type is asserted against. If you want to do your own type-checking (or want to build a matcher that works with multiple types) you can use `func(actual any) (bool, err)` instead.
1727+
- Rather than implement different functions for the two different failure messages you can provide a single template. `gcustom` provides template variables to help you render the failure messages depending on positive failures vs negative failures. For example, the variable `{{.To}}` will render "to" for positive failures and "not to" for negative failures.
1728+
- You can pass additional data to your template with `WithTemplateData(<data>)` - in this case we pass in the expected object so that the template can include it in the output. We do this with the expression `{{format .Data 1}}`. gcustom provides the `format` template function to render objects using Ginkgo's object formatting system (the `1` here denotes the level of indentation).
1729+
1730+
`gcustom` also supports a simpler mechanism for generating messages: `.WithMessage()` simply takes a string and builds a canned message out of that string. You can also provide precompiled templates if you want to avoid the cost of compiling a template every time the matcher is called.
1731+
1732+
### Testing CUstom Matchers
1733+
1734+
Whether you create a new `representJSONMatcher` type, or use `gcustom` you might test drive this matcher while writing it using Ginkgo. Your test might look like:
16931735

16941736
```go
16951737
package json_response_matcher_test

0 commit comments

Comments
 (0)