1

I am using Cucumber GoDog as a BDD test framework for gRPC microservice testing. GoDog does not come with any assertion helpers or utilities.

Does anyone here have experience adopting any of the existing assertion libraries like Testify/GoMega with GoDog?

As far as I know GoDog does not work on top of go test which is why I guess it's challenging to adopt any go test based assertion libraries like I mentioned. But I would still like to check here if anyone has experience doing so.

Abhijeet Vaikar
  • 1,578
  • 4
  • 27
  • 50

4 Answers4

1

Here's a basic proof-of-concept using Testify:

package bdd
import (
    "fmt"
    "github.com/cucumber/godog"
    "github.com/stretchr/testify/assert"
)
type scenario struct{}
func (_ *scenario) assert(a assertion, expected, actual interface{}, msgAndArgs ...interface{}) error {
    var t asserter
    a(&t, expected, actual, msgAndArgs...)
    return t.err
}
func (sc *scenario) forcedFailure() error {
    return sc.assert(assert.Equal, 1, 2)
}
type assertion func(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
type asserter struct {
    err error
}
func (a *asserter) Errorf(format string, args ...interface{}) {
    a.err = fmt.Errorf(format, args...)
}
func FeatureContext(s *godog.Suite) {
    var sc scenario
    s.Step("^forced failure$", sc.forcedFailure)
}
Feature: forced failure
  Scenario: fail
    Then forced failure

The key here is implementing Testify's assert.TestingT interface.

Snargleplax
  • 185
  • 1
  • 5
  • I liked the idea, but I would prefer to stick with `asserter` instance passing directly to the testify assertion method: `assert.Contains(&t, lastOutput, text); return t.err` – voiski Jun 07 '21 at 21:35
1

Here's a proof of concept with GoMega:

Register the GoMega Fail Handler before running any tests to have GoMega simply panic with the error message.

gomega.RegisterFailHandler(func(message string, _ ...int) {
    panic(message)
})

Define a step fail handler to recover from any fails.

func failHandler(err *error) {
    if r := recover(); r != nil {
        *err = fmt.Errorf("%s", r)
    }
}

Now at the beginning of every step definition defer running the failHandler like so:

func shouldBeBar(foo string) (err error) {
    defer failHandler(&err)

    Expect(foo).Should(Equal("bar"))

    return err
}

Now if/when the first of our GoMega assertion fails, the step function will run the failHandler and return the GoMega failure message (if there is one). Notice we are using named result parameters to return the error, see How to return a value in a Go function that panics?

Matt Simons
  • 384
  • 3
  • 8
  • This one complements the [testify one](https://stackoverflow.com/a/61528852/2985644) by [snargleplax](https://stackoverflow.com/users/341622/snargleplax) as good answers to this question besides the accepted one. Those are straightforward of how you can make it in both mentioned libraries Testify/Gomega. – voiski Jun 07 '21 at 21:55
0

sorry to see you're still working on this.

As we chatted before, there is a way to get it working via the link I sent you before, it's just not necessarily a beginner friendly setup as you mentioned in Slack. Perhaps this is something us contributors can look into in the future, it's just not something that's set up currently and since we're mostly volunteers, setting up timelines on new features can be tough.

My recommendation for the time being would be to do assertions via if statements. If you don't want them in your test code specifically, then you can make a quick wrapper function and call them that way.

Jayson Smith
  • 51
  • 1
  • 2
0

Had the same question today, trying to integrate gomega with godog. And thanks to Go's simplicity I managed to get something to work (this is my third day with Go :-). Allthough I think this isn't going to work in real world projects, yet, I'd like to share my thoughts on this.

Coming from Rails/RSpec, I like having compact test cases/steps without boiler plate code. So I tried to put handling failures out of the steps and into before/after hooks:

func InitializeGomegaForGodog(ctx *godog.ScenarioContext) {
    var testResult error
    ctx.StepContext().Before(func(ctx context.Context, st *godog.Step) (context.Context, error) {
        testResult = nil
        return ctx, nil
    })
    ctx.StepContext().After(func(ctx context.Context, st *godog.Step, status godog.StepResultStatus, err error) (context.Context, error) {
        return ctx, testResult
    })
    gomega.RegisterFailHandler(func(message string, callerSkip ...int) {
        // remember only the expectation failed first
        // anything thereafter is not to be believed
        if testResult == nil {
            testResult = fmt.Errorf(message)
        }
    })
}

func InitializeScenario(ctx *godog.ScenarioContext) {
    InitializeGomegaForGodog(ctx)

    ctx.Step(`^incrementing (\d+)$`, incrementing)
    ctx.Step(`^result is (\d+)$`, resultIs)
}

Of course, this aproach will not stop steps where expectations didn't match. So there's a risk of having undefined behavior in the rest of the step. But the steps' implementations are quite simple with this approach:

func resultIs(arg1 int) {
    gomega.Expect(1000).To(gomega.Equal(arg1))
}
couchbelag
  • 11
  • 1