Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the context accessible in before/after scenarios #50

Closed
sagikazarmark opened this issue Jan 17, 2020 · 13 comments · Fixed by #76
Closed

Make the context accessible in before/after scenarios #50

sagikazarmark opened this issue Jan 17, 2020 · 13 comments · Fixed by #76
Labels
enhancement New feature or request

Comments

@sagikazarmark
Copy link
Collaborator

Is your feature request related to a problem? Please describe.
Given I have a service that I have to initialize and want to do that per scenario run (so that each scenario can run with a clean state).

I would like to initialize this service and make it available to the steps.

Describe the solution you'd like
Since currently context is the way to share state between step functions and before scenario seems to be the logical place for initialization, it should probably receive the context.

On second thought, I'm not 100% how before suite hook is actually useful without this.

Describe alternatives you've considered
One could have a Given statement in Background for initialization, but that would be ugly.

Additional context
Probably the same is true for after suite hooks.

@sagikazarmark sagikazarmark added the enhancement New feature or request label Jan 17, 2020
@bkielbasa
Copy link
Collaborator

yeah, that can be useful. Initially, "before the scenario" was used to clean up some DB tables etc so I didn't need any context here but it sounds reasonable.

@bkielbasa
Copy link
Collaborator

@sagikazarmark how do you want to give access to the service? Do you want to put it to the context or use some kind of "global state"?

@sagikazarmark
Copy link
Collaborator Author

I kinda like the "previous" context approach, where I could just mutate the context and set a value. IMHO it's definitely better than using global state.

The alternative is registering member functions of a struct, but then I have to manually maintain separate instances of my service for the different scenarios.

@bkielbasa
Copy link
Collaborator

hmm, I will get a second opinion about it and then we'll make a decision.

The reason why I changed it was that I could provide only support for basic types and to support them I had to write a few code generators what looked a bit hacky to me (a lot of copy&pasted code). Without it was just a regular map hidden behind an interface. That's what the pkg context is really about.

@sagikazarmark
Copy link
Collaborator Author

Yeah, I get that, but I think the context in this case is not something that propagates through a request, but a central state store everything goes to, so IMHO a purpose-built structure is justified.

Also, I would maximize for readability even at the cost of "architectural correctness", because it's an incredibly important property of tests.

@bkielbasa
Copy link
Collaborator

I've been thinking about it. Could you give me an example scenario where you needed the context in the before/afterstep?

Here's how I used it: I create a DB connection in BeforeScenario, I use Background to fill it in with required data (some initial products etc) and then execute the scenario. In AfterScenario I clean up tables.

So it looks similar to:

Background:
  I have a list of products in my shop #here I add the product and the user I work on
Scenario: adding a product to the basket
  Given I'm logged in user
  When I add the product "abc" to the bucket
  Then the total price in the basket should be equal to 123
  When I add the product "cba" to the bucket
  Then the total price in the basket should be equal to 222

In before/after, I just do the more "technical" work like creating connections to db/queue etc. What's your approach? I want to understand the need better to make a better decision :)

@sagikazarmark
Copy link
Collaborator Author

I create a DB connection in BeforeScenario

How do you access the created db connection in your steps?

I'd like to avoid creating global instances.

Also, I would probably like to instantiate a service instance at the beginning of every scenario, not just the DB.

So I would instantiate a service in a before scenario, set it in the context, and call service methods in steps.

@mirogta
Copy link
Collaborator

mirogta commented Feb 6, 2020

Just to support this for our use cases:

  • Background runs before each scenario
  • BeforeScenario also runs before each scenario but it's "hidden" (not visible in the feature files)
  • AfterScenario runs after each scenario and it's also "hidden"
  • There's no concept of Teardown (the opposite of Background) which would be visible in feature files and would run after each scenario?

I understand the point to hide technical, non-interesting code into the Before and After hooks, even though we don't like to hide stuff.

I would personally prefer not to use BeforeScenario and AfterScenario at all and put the whatever initialisation in the Background implementation, so that my colleagues have a hint that there is some initialisation to look for. But because there's no Teardown we end up doing this in the hooks and typically use some vague Background to indicate that there is a Before/After hook to look for as well.

Same as @sagikazarmark , it would involve service(s), not just a DB connection and we hate to create global instances to make it work - currently with using godog. So accessing context in the hooks, would be crucial for us.

@bkielbasa
Copy link
Collaborator

can you give an example scenario? I want to understand you better

@sagikazarmark
Copy link
Collaborator Author

sagikazarmark commented Feb 19, 2020

Here is what I have in mind:

func TestScenario(t *testing.T) {
	suite := bdd.NewSuite()

	suite.BeforeSuite(func(ctx Context) {
		db := NewDB()

		ctx.Set("db", db)
	})

	suite.BeforeScenario(func(ctx Context) {
		service := NewService(ctx.Get("db"))

		ctx.Set("service", service)
	})

	suite.AfterSuite(func(ctx Context) {
		db := ctx.Get("db")

		db.Cleanup()
		db.Close()
	})

	// Here is the important bit:
	// functions are not inline, they don't have access to the context of TestScenario
	suite.Step(serviceCallStep)
	suite.Step(verifyStep)
}

func serviceCallStep(ctx Context) {
	service := ctx.Get("service")

	err := service.Call()

	ctx.Set("error", err)
}

func verifyStep(ctx Context) {
	err := ctx.GetError("error")

	if err != nil {
		panic("the universe is broken")
	}
}

Technically, these are not business actions (setting up a service is a purely technical one. Background is for things like setting initial state)

@bkielbasa
Copy link
Collaborator

when the serviceCallStep and verifyStep will be called? Are there just regular steps? I didn't get this part :)

@sagikazarmark
Copy link
Collaborator Author

Yes, they are simple steps

@bkielbasa
Copy link
Collaborator

ok, let's go for it :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants