Godog pass arguments/state between steps - go

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.

Related

Run function from a package by reflecting its name

Currently I have the package name as a string "Forecast" and I need to reflect this string into a package so that I can call the function Run(). Is there a way to make this reflection?
Why?
Currently I am building a task runner in golang in which all tasks have the function Run() and I receive which task to run by a kafka message "task": "Forecast", so I am trying to avoid a switch like:
switch message.Task {
case "Forecast":
Forecast.Run()
case "SupplyCalculator":
SupplyCalculator.Run()
}
And instead to just reflect the name and call the function, like this (PHP):
$task = new ReflectionClass("\\Task\\{$message->task}");
$task->run();
Packages are not a type in Go.
Given a package foo with a function Run, this works...
v := reflect.ValueOf(foo.Run)
fmt.Println(v.Kind()) // func
But this is a syntax error:
v := reflect.ValueOf(foo)
Instead of trying to use reflection, register the functions ahead of time in a map, and then look up the correct function in that map to call it. You can provide a simple tasks package to do this, with methods like Register and Run.
// tasks.go
package tasks
type TaskFunc func() error // or whatever arguments your tasks take
var taskFuncs = map[string]TaskFunc{}
func Register(name string, fn TaskFunc) {
taskFuncs[name] = fn
}
func Run(name string) error {
if fn, found := taskFuncs[name]; found {
return fn()
}
return fmt.Errorf("Task %q not found", name)
}
// forecast.go
package forecast
import "tasks"
tasks.Register("forecast", Run)
func Run() error {
// ...
}
// main.go
err := tasks.Run(message.Task)

Using an assertion library with GoDog test framework

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))
}

Function pointer as argument with "return interface{}"

I would like to pass a function pointer to a function to "anything".
It's easy to print something that gets passed in from just about anything (as in https://play.golang.org/p/gmOy6JWxGm0):
func printStuff(stuff interface{}) {
fmt.Printf("Testing : %v", stuff)
}
Let's say, though, that I want to do this:
Have multiple structs
Have data loaded from various functions
Have a generic print that calls the function for me
I tried this in a Play (https://play.golang.org/p/l3-OkL6tsMW) and I get the following errors:
./prog.go:35:12: cannot use getStuff1 (type func() SomeObject) as type FuncType in argument to printStuff
./prog.go:36:12: cannot use getStuff2 (type func() SomeOtherObject) as type FuncType in argument to printStuff
In case the Play stuff gets deleted, here's the code I'm trying to figure out how to get to work:
package main
import (
"fmt"
)
type SomeObject struct {
Value string
}
type SomeOtherObject struct {
Value string
}
type FuncType func() interface{}
func getStuff1() SomeObject {
return SomeObject{
Value: "Hello, world!",
}
}
func getStuff2() SomeOtherObject {
return SomeOtherObject{
Value: "Another, hello!",
}
}
func printStuff(toCall FuncType) {
stuff := toCall()
fmt.Printf("Testing : %v", stuff)
}
func main() {
printStuff(getStuff1)
printStuff(getStuff2)
}
What is the secret sauce to get this stuff passed in properly?
Larger Goal Explanation
So what I am trying to accomplish here is reduction of boilerplate code that lives inside a gigantic file. Unfortunately I cannot refactor it further at this point due to other restrictions and I was wondering if this were possible at all considering the error messages and what I had read seemed to dictate otherwise.
There's a large amount of copy-and-paste code that looks like this:
func resendContraDevice(trap *TrapLapse, operation *TrapOperation) {
loaded := contra.Load()
err := trap.SnapBack(operation).send(loaded);
// default error handling
// logging
// boilerplate post-process
}
func resendPolicyDevice(trap *TrapLapse, operation *TrapOperation) {
loaded := policy.Load()
err := trap.SnapBack(operation).send(loaded);
// default error handling
// logging
// boilerplate post-process
}
// etc.
In these, the Load() functions all return a different struct type and they are used elsewhere throughout the application.
I want hoping to get something where I could have:
loaded := fn()
err := trap.SnapBack(operation).send(loaded);
// default error handling
// logging
// boilerplate post-process
Signature for send is, which accepts an interface{} argument:
func (s SnapBack) send(data interface{}) error
I don't know if you have control over the return values of contra.Load() and policy.Load(), for instance, so there may be a better approach, but assuming those cannot be modified, this would allow you to eliminate a lot of boilerplate, without any fancy manipulation:
func boilerplate(tram *TrapLapse, operation *TrapOperation, loader func() interface{}) {
loaded := loader()
err := trap.SnapBack(operation).send(loaded);
// default error handling
// logging
// boilerplate post-process
}
func resendContraDevice(trap *TrapLapse, operation *TrapOperation) {
boilerplate(trap, operation, func() interface{} { return contra.Load() })
}
func resendPolicyDevice(trap *TrapLapse, operation *TrapOperation) {
boilerplate(trap, operation, func() interface{} { return policy.Load() })
}
If there's nothing more complex, you can also simplify this even further:
func boilerplate(tram *TrapLapse, operation *TrapOperation, loaded interface{}) {
err := trap.SnapBack(operation).send(loaded);
// default error handling
// logging
// boilerplate post-process
}
func resendContraDevice(trap *TrapLapse, operation *TrapOperation) {
boilerplate(trap, operation, contra.Load())
}
func resendPolicyDevice(trap *TrapLapse, operation *TrapOperation) {
boilerplate(trap, operation, policy.Load())
}

Context without cancel propagation

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.

What is the idiomatic way to wrap a type?

I want to wrap goquery.Selection for getting HTML and selector string more conveniently.
To access methods of goquery.Selection, should I implement some helper method such as Get() on the following code?
type MySelection goquery.Selection
// Without this helper method, I should always use type conversion
// to use goquery.Selection's methods.
func (s *MySelection) Get() *goquery.Selection {
sel := s.(goquery.Selection)
return sel
}
func (s *MySelection) HTML() string {
// html, _ := s.Html() <- error
html, _ := s.Get().Html()
return html
}
func (s *MySelection) String() string {
return fmt.Sprintf("%v#%v.%v",
goquery.NodeName(s.Get()),
s.Get().AttrOr("id", "(undefined)"),
s.Get().AttrOr("class", "(undefined)"))
}
Are there better ways to handle this situation?
You also can embed
type MySelection struct {
goquery.Selection
some payload //if needed
}
and you will have goquery.Selection methods for MySelection for free and can to add or overwrite some.
Well, there are several ways to "handle this." But don't name it Get(): it isn't idiomatic.
From a best-practices perspective, I would recommend:
Decouple their code from your code.
Implementing an Anti-Corruption Layer (a wrapper that wraps their package)
The reasons for this is many. But for Go, it's best to keep it simple - which boils down to one question: do you want to unit test your code?
If the answer is yes, then I would never use a 3rd party package directly. I'd wrap their package with my own interface. Then, use use (inject) that interface throughout all of my code so to allow me to mock it up in unit tests.
Again there are several patterns and opinions; but, i am going to show this one of a wrapper allowing for unit testing.
goquery_wrapper.go
package mypackage
import (
"path/to/goquery.Selection"
)
var _mySelector *mySelector // Go stdlib uses underscores for private types
type mySelector interface {
Html() string
...
}
type MySelector struct {
}
func (ms *MySelector) Html() {
// your custom version
}
// initialize the global var with your wrapper
func init() {
_mySelector = &MySelector{ ... }
}
foo.go
package mypackage
func Foo() {
// uses the global var initialized with init()
data := _mySelector.Html()
// IoC using D.I. through constructors
obj := NewSomething(_mySelector)
// IoC using D.I. through methods
result := bar.Process(_mySelector, "input data")
}
bar_test.go
package mypackage
import (
"testing"
)
type mockSelector struct {
HtmlWasCalled bool
HtmlReturnsThis string
}
func (ms mockSelector) Html() string {
ms.HtmlWasCalled = true
return ms.HtmlReturnsThis
}
func TestBar(t *testing.T) {
// arrange
// override your global var
oldMS := _mySelector
_mySelector := &mockSelector{
HtmlReturnsThis: "<b>success</b>",
}
// act
// since foo.Bar is using the global var, it now uses
// our mock we set above.
result := foo.Bar("sample input")
// assert
if result != expected {
t.Fail()
}
// put it back the way it was
_mySelector = oldMS
}
func TestFoo(t *testing.T) {
// arrange
mock := &mockSelector{
HtmlReturnsThis: "<b>success</b>",
}
// act
// or, just inject your mock if using IoC
result := bar.Process(mock, "sample input")
// assert
...
}
This decouples me from having to deal with the 3rd party package nuances during unit testing. Works well, except when the API of the package is huge. Then, I question even why I am using the package to begin with if it is that complicated.

Resources