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.
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.
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?
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.
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))
}
Related
To comply with the concurrency requirements, I'm wondering how to pass arguments or a state between multiple steps in Godog.
func FeatureContext(s *godog.Suite) {
// This step is called in background
s.Step(`^I work with "([^"]*)" entities`, iWorkWithEntities)
// This step should know about the type of entity
s.Step(`^I run the "([^"]*)" mutation with the arguments:$`, iRunTheMutationWithTheArguments)
The only idea which comes to my mind is to inline the called function:
state := make(map[string]string, 0)
s.Step(`^I work with "([^"]*)" entities`, func(entityName string) error {
return iWorkWithEntities(entityName, state)
})
s.Step(`^I run the "([^"]*)" mutation with the arguments:$`, func(mutationName string, args *messages.PickleStepArgument_PickleTable) error {
return iRunTheMutationWithTheArguments(mutationName, args, state)
})
But this feels a bit like a workaround. Is there any feature in the Godog library itself to pass those information?
I've found good luck using methods instead of functions for the steps. Then, putting state in the struct.
func FeatureContext(s *godog.Suite) {
t := NewTestRunner()
s.Step(`^I work with "([^"]*)" entities`, t.iWorkWithEntities)
}
type TestRunner struct {
State map[string]interface{}
}
func (t *TestRunner) iWorkWithEntities(s string) error {
t.State["entities"] = s
...
}
Godog doesn't currently have a feature like this, but what I've done in the past in general (would need to be tested for concurrency) would be to create a TestContext struct to store data in and create a fresh one before each Scenario.
func FeatureContext(s *godog.Suite) {
config := config.NewConfig()
context := NewTestContext(config)
t := &tester{
TestContext: context,
}
s.BeforeScenario(func(interface{}) {
// reset context between scenarios to avoid
// cross contamination of data
context = NewTestContext(config)
})
}
I have a link to an old example here as well: https://github.com/jaysonesmith/godog-baseline-example
Latest version (v0.12.0+) of godog allows chaining context.Context between hooks and steps.
You can have context.Context as step definition argument and return, test runner will provide context from previous step as input and use returned context to pass to next hooks and steps.
func iEat(ctx context.Context, arg1 int) context.Context {
if v, ok := ctx.Value(eatKey{}).int; ok {
// Eat v from context.
}
// Eat arg1.
return context.WithValue(ctx, eatKey{}, 0)
}
Additional information and examples: https://github.com/cucumber/godog/blob/main/release-notes/v0.12.0.md#contextualized-hooks.
While I am trying to learn Go (version 1.13) I was trying to implement basic network operation but I got tired with if err != nil and decided to be a bit smarter and created something like this
package operation
type Op struct {
err error
result interface{}
}
func Do(supplier func() (interface{}, error)) *Op {
result, err := supplier()
return &Op {result: result, err: err}
}
func (op *Op) onSuccessMap(supplier func(input interface{}) (interface{}, error)) *Op {
if op.err != nil {
return op
} else {
r, e := supplier(op.result)
return &Op{result: r, err: e}
}
}
func (op *Op) onFailure(errorHandler func(error) (interface{}, error)) *Op {
if op.err == nil {
return op
} else {
newResult, err := errorHandler(op.err)
return &Op{result: newResult, err: err}
}
}
func (op *Op) get() (interface{}, error) {
return op.result, op.err
}
and I wanted to call it like
r,e := operation.Do(func()(*http.Request, error){
return http.NewRequest("GET", "http://target.com", nil)
})
.onSuccessMap(func(req *http.Request)(*http.Response, error) {
//... do something with request
})
.onSuccessMap(func(req *http.Response)(MyDomain, error) {
//... do something with response
})
.onFailure(func(e error) (interface{}, error) {
// .. optionally do something with first error
}).get()
but is seems it is not that simple :)
I cannot pass func()(*http.Request, error) where func()(interface{}, error) is expected
anyway Do(func() (string, error) {return "a", nil}.. dont compile either
I actually dont care what is supplied to previous call if next caller can handle it - probably some kind of generics would be handy but I didn't find anything in docs about it.
I cannot call .onSuccessMap/.onXXX/.. probably because I return *Op rather then Op - should I explicitly dereference *Op, maybe I shouldn't return *Op in first place
can I somehow simplify suppliers function so I dont have to pass all those information - it would be nice if compiler figure out what is going on
maybe I think too much in java way and not go way,
any comments/hints will be highly helpful. maybe the whole idea doesn't make sense :]
This Q&A is very much opinion-based, but my suggestion is do not mplement your own error handlers in this fashion.
Since you are you using go 1.13, I would highly recommend you read the new error handling features in the go-blog.
It allows for chaining errors, and easily unwrapping any particular error type from the error-chain. With errors.Is one can determine if a particular error type occurred. With errors.As you can extract the exact details of that error.
fmt.Errorf with %w is a quick way to generate your own wrapped errors. And if you want more error details, you can simply write your own error types provided they include the Error and Wrap interfaces.
How can I create a copy (a clone if you will) of a Go context that contains all of the values stored in the original, but does not get canceled when the original does?
It does seem like a valid use case to me. Say I have an http request and its context is canceled after the response is returned to a client and I need to run an async task in the end of this request in a separate goroutine that will most likely outlive the parent context.
func Handler(ctx context.Context) (interface{}, error) {
result := doStuff(ctx)
newContext := howDoICloneYou(ctx)
go func() {
doSomethingElse(newContext)
}()
return result
}
Can anyone advice how this is supposed to be done?
Of course I can keep track of all the values that may be put into the context, create a new background ctx and then just iterate through every possible value and copy... But that seems tedious and is hard to manage in a large codebase.
Since context.Context is an interface, you can simply create your own implementation that is never canceled:
import (
"context"
"time"
)
type noCancel struct {
ctx context.Context
}
func (c noCancel) Deadline() (time.Time, bool) { return time.Time{}, false }
func (c noCancel) Done() <-chan struct{} { return nil }
func (c noCancel) Err() error { return nil }
func (c noCancel) Value(key interface{}) interface{} { return c.ctx.Value(key) }
// WithoutCancel returns a context that is never canceled.
func WithoutCancel(ctx context.Context) context.Context {
return noCancel{ctx: ctx}
}
Can anyone advice how this is supposed to be done?
Yes. Don't do it.
If you need a different context, e.g. for your asynchronous background task then create a new context. Your incoming context and the one of your background task are unrelated and thus you must not try to reuse the incoming one.
If the unrelated new context needs some data from the original: Copy what you need and add what's new.
In my exploration with Golang, I have encountered different ways of returning values from a method. To make this easier, I'll play off an example. Assume that we have two methods that return a single Corgi struct and a slice of Corgi.
Coming from Java/C# land, one way of achieving this is to include the value or pointer as part of the return with something like this:
func GetCorgi(id int) (Corgi, error) {
// Do stuff and return a Corgi struct or error
}
func GetAllCorgis() ([]Corgi, error) {
// Do stuff and return a slice of Corgi structs or error
}
However, I've noticed other APIs like App Engine and MongoDB, use this approach for some of their methods where you pass a pointer which then gets populated.
App Engine Get
func Get(c context.Context, key *Key, dst interface{}) error
Mongo One and All
func (q *Query) One(result interface{}) (err error)
func (q *Query) All(result interface{}) error
Which in my case may look like this
func GetCorgi(id int, corgi *Corgi) error {
// Populate corgi and return error
}
func GetAllCorgis(corgis *[]Corgi) error {
// Populate corgi and return error
}
Is this a matter of preference/style? Or are there advantages with one approach?
Let's say I have this interface
type Selecter interface {
Select(vars ...string) error
}
and I want to make handlers that pretty much just return JSON forms of that interface after it has called the Select function. Like this:
func MakeHandler(s Selecter) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//... do some stuff to get vars ...
if err := s.Select(v1, v2); err != nil {
//... blah blah errors ...
}
b, err := json.Marshal(s)
if err != nil {
//... blah blah errors ...
}
w.Write(b)
}
}
So if CoolType is a Selecter I can do something like this
type CoolType struct {
CoolString string `json:"cool_string"`
CoolInt int `json:"cool_int"`
CoolBool bool `json:"cool_bool"`
}
func (c *CoolType) Select(vars ...string) error {
// fill up c using vars
return nil
}
// this looks kinda ugly to me too
fn := MakeHandler(CoolType{})
The underlying problem I have with this is that s is an interface and uses a pointer. This would make this not safe in goroutines since s could be modified between the calls to Select and the call to Marshal.
I really think this is the way I'd like to go about implementing this since it is fairly concise and easy for me to change, but I think I'm missing something. I could use reflect or change the Selecter interface to have Select return an interface{} instead since I don't particularly care what the type is. Then I'd just make a new copy of the type in every implementation of Select I guess. Or a mutex would work. Or perhaps the better way to do this would be to have all of my Selecter types just implement ServeHTTP and be an http.Handler.
Anyway I assume people have tried something like this and have come up with possibly more elegant solutions so I'd like to hear some ideas.
If you are afraid of mutations pass a copy of the value. To do it you probably have to change your interface to something like:
type Selecter interface {
Select(vars ...string) (Selecter, error)
}
And change your Select method to take a value receiver to fulfill the interface. It would be func (c CoolType) Select(vars ...string) (CoolType, error)
Then pass your s as a value instead of pointer and call Select like:
if s, err := s.Select(v1, v2); err != nil {
//... blah blah errors ...
}
You can argue that you lose information about the type, but you already lost it while passing the value as a Selecter interface.
But in general your implementation is good. My feeling is that in Go elegant is something different than in other languages. I'd say that in Go elegant is readable, maintainable and accurate - not bureaucracy and trying to protect you from yourself.