gomega: RFC on error assertions for functions with multi-return values and trailing error value
This is a request for comment on extending Gomega to more smoothly handle (phrase) error assertions for functions which return multiple values, including a trailing error. While working on my Gomega 3rd party extension “errxpect” (https://github.com/TheDiveO/errxpect) I noticed some things and I would like to get feedback on ways to do it better or if gomega could be opened to 3rd party implementors of gomega.Assertion.
So, instead of:
_, _, err := foo(42)
Expect(err).To(MatchError("DOH!"))
simply spell it out as:
Errxpect(foo(42)).To(MatchError("DOH!"))
Things I noticed and had to work around:
-
Gomega’s internal implementation of
gomega.Assertionbasically does two separate things of relevance to our discussion here:- it implements the important logic for calling a fail handler and generating useful stack context information – which is something that any 3rd party implementation of the
gomega.Assertionprobably would like to tap into (“inherit”). - specific multi-return value checking in form of a single actual parameter, and a extra… parameters. Please note that this check conflicts with my specific usecase of having a non-zero error, or a zero error with potentially non-zero other return values.
- it implements the important logic for calling a fail handler and generating useful stack context information – which is something that any 3rd party implementation of the
-
Gomega’s
ExpectWithOffsetpattern cannot be applied to multi-return value functions. This actually can be easily remedied with a genericWithOffset(o int)function added to thegomega.Assertioninterface. In my errexpect module I return a more specially typed assertion object, which then offersWithOffset(...), so I don’t need to touch Gomega’sAssertioninterface.
My questions for feedback:
-
At the moment I have to get Gomega’s assertion functionality for triggering fail handlers and producing stack context information by running a transient, separate assertion when checking the multiple return values for calling the user’s matcher(s). Could you imagine opening up the functionality for triggering fail handlers and stack context traces to 3rd party implementers of
Assertion? -
With me coming up with
ErrxpectI’m throwing stones in my glasshouse … but sinceExpectWithOffset(...)slightly stutters and with Option receivers being all the rage, could you imagine a genericAssertion.WithOffset(o), so Gomega’s assertions/expectations can also be spelled out asExpect(...).WithOffset(1).To(...)?
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 1
- Comments: 23 (10 by maintainers)
❤️ thanks for the kind shout out!
Once you get the
WithTransformPR in I’ll a cut a release.Unrelated BTW I just pushed a new
HaveField()matcher that I think will make it a bit easier to write custom matchers. It’ll be in the next release too!We can provide
ItAllEndsInTears()for that.hey - I just went back and reread the original issue up top and am realizing that I’ve misunderstood what you were proposing all along! I must have been reading it quickly or jumped through a bunch of unspoken assumptions on my part, but I ended up thinking the use case is “making arbitrary assertions against multiple return values within a single
Expectcall”.But now I see that the use case is “there are times when I want to assert that an error has occurred (or matches a specific error) and that all the other return values are nil/zero-valued” - which… makes more sense.
Sorry for the confusion! I generally agree that
v1, v2, v3, err := foo()is just clearer in the multi-return value use case.Now that we’re on the same page (my bad!) I can see adding
Expect().Error()as a simple mechanism to accomplish this and we wouldn’t need any additional chaining. The user is simply choosing between the default behavior “Assert on v1, ensure all others are niladic” and error-matching behavior “Assert on the last return value, ensure all others are niladic”. And we could just stop there and see what further patterns emerge.Sorry again for misreading your original issue. It’s been quite the journey to get here but I think we’re on the same page now!
I’m imagining it looks like overhauling the implementation over at https://github.com/onsi/gomega/blob/master/internal/assertion/assertion.go to enable the chaining I outlined above. I can get to this eventually, so no worries - though i’m trying to focus my free coding time (which there isn’t too much of due to covid and in-person schooling being closed!) on Ginkgo 2.0 right now… but if you have time to give it a shot, I’d start there.
Hey @TheDiveO sorry for the delay.
I like pulling out
.WithOffsetand, more broadly, appreciate the problem you’re trying to solve around Gomega better handling multiple return values. I wonder if there’s a clean backwards compatible (i.e. can we avoid a major version bump) way to extendExpectto handle multiple return values more explicitly - and basically pull in what you’re accomplishing withErrxpectinto the core.Here’s a first sketch of what this might look like. We keep the default behavior but extend
AssertionandAsyncAssertionto enable users to explicitly call out individual return values and then apply assertions. So maybe something like:I think that could be pulled off in a largely backward compatible way. Thoughts?