Hooks & Tags
Hooks​
Earlier we saw how Backgrounds could be used remove duplication between scenarios. This is similar to a setup method, but only for concepts that make sense to the business. SpecFlow provides hooks so you can run technical setup/teardown code that doesn’t belong in your business-level Gherkin, such as:
- starting or stopping a web server
- cleaning up temp files
- truncating a database table
- C#
- JavaScript
- Go
Peruse the hooks documentation at your leisure.
A hook is simply a public method annotated with one of the following:
[BeforeScenario]: runs before each scenario[AfterScenario]: runs after each scenario
You can have as many hooks as you need.
Create a ShoutyHooks class next to your step definition classes and create a BeforeScenario hook
and an AfterScenario hook in it.
Remember the [Binding] attribute we added to our new step definition class earlier? SpecFlow
also needs this attribute to find hooks.
Make each hook print a different string to the console using Console.WriteLine.
Create a shouty_hooks.js file in the features/support directory and add a Before and After hook:
const { Before, After } = require('@cucumber/cucumber')
Before(function () {
console.log('Before scenario (from shouty_hooks.js)')
})
After(function () {
console.log('After scenario (from shouty_hooks.js)')
})
You can define as many hooks as you need across your support files — Cucumber will pick them all up when it loads the suite. Run SpecFlow and watch the console output to see when the hooks fire relative to your scenarios.
Hooks are special functions that you can define to run some code at specific points in the test cycle. These hooks allow you to set up or tear down test environments, prepare data, or perform cleanup activities. They are crucial for managing the context and state of your scenarios.
Before: runs before each scenarioAfter: runs after each scenarioBeforeSuite: runs before the test suiteAfterSuite: runs after the test suite
We use a Before hook our shouty tests to create a new instance of Shouty before each scenario.
sc.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
shouty = NewShouty() // Reset Shouty before each scenario
return ctx, nil
})
Try it yourself:
- In
func InitializeScenario, create aBeforehook and anAfterhook on theScenarioContextstruct. - In
func InitializeSuite, create aBeforeSuitehook and anAfterSuitehook onTestSuiteContext.
Make each hook print a different string to the console using fmt.Println.
Run SpecFlow. Is the output on the console as you expected?
Due to the way output to the screen is buffered, your print statements may not interleave as expected with SpecFlow’s output. Check that they’re in the right order
Ordered Hooks​
- C#
- JavaScript
- Go
Now add another set of BeforeScenario and AfterScenario hooks. What order are the two BeforeScenario and AfterScenario hooks firing in?
There are situations when the order that hooks run in is critical, and SpecFlow gives you some control over this.
This is done by supplying an integer with the annotation: [BeforeScenario(Order = 4)]
BeforeScenario hooks execute in ascending order, AfterScenario hooks also execute in ascending order.
If you don’t specify an order in the annotation, SpecFlow defaults it to 10000. Remove the order attribute from one of your annotations to confirm this.
We recommend that you define related BeforeScenario and AfterScenario hooks next to each other.
Now add another pair of Before and After hooks (either in the same file or a different support file) and watch the order they run in.
In Cucumber.js:
Beforehooks run in the order they are definedAfterhooks run in the reverse order they are defined
For example:
Before(function () {
console.log('First Before')
})
Before(function () {
console.log('Second Before')
})
After(function () {
console.log('First After')
})
After(function () {
console.log('Second After')
})
will log:
- First Before
- Second Before
- scenario steps...
- Second After
- First After
What happens if you also add a Before/After hook in shout_steps.js? Does it run before or after the hooks in shouty_hooks.js? Why?
Godog does not support ordered hooks. If you need to run hooks in a specific order, you will need to combine them into a single hook.
Tags
SpecFlow allows you to add free-text tags to your features, scenarios, scenario outlines and example tables (see Scenario Outline sections):
- tags are an
@character followed by some text (without any white space) - tags applied to a feature apply to every scenario in that feature
- tags are case sensitive
- you can apply as many tags to a feature/scenario as you wish
- C#
- JavaScript
- Go
SpecFlow converts the tags into "Traits" or "Categories" which you can use to selectively run scenarios in your test-runner.
Lets use the VisualStudio Test Explorer window to just run a single "Work in Progress" scenario.
- Add a
@wiptag to one of the scenarios - Run SpecFlow to confirm all scenarios are still green
- Open the Test Explorer window
- Type
Trait:wipin to the Search text box, and press enter Run allwill now only execute the scenario you tagged as@wip
You can use tags to select which scenarios to run in Cucumber. Tags are free-text labels starting with @, added to features, scenarios, scenario outlines, or example tables.
Let’s use a tag to mark a “Work in Progress” scenario:
Add a @wip tag above one of your scenarios:
@wip
Scenario: In range shout is heard
...
From the command line, run just the tagged scenarios, for example:
npm run cucumber -- --tags @wip
(Adjust the script name if your package.json uses a different command.)
Cucumber will now only execute the scenario(s) tagged with @wip.
You can also use logical tag expressions, for example:
npm run cucumber -- --tags "@ShoutHeard or @ShoutNotHeard"
to run any scenario tagged with @ShoutHeard or @ShoutNotHeard.
Tag expressions are modeled after Behat's tag behavior
What happens if you add multiple tags to a scenario? How do you run only scenarios with multiple tags?
If you want to run only part of your suite, or some scenarios, you can do it with tag filters.
- Add a
@wiptag to one of the scenarios - Run
go test --godog.tags=wipto confirm only the tagged scenario is executed - Remove the
@wiptag and rungo test --godog.tags=wipto confirm no scenarios are executed
Tag expressions can be used to specify multiple tags, and to exclude tags. For example, --godog.tags=wip,~slow will run all scenarios tagged with @wip but not @slow.
What happens if you add multiple tags to a scenario? How do you run only scenarios with multiple tags?
Tag expressions in Godog are modeled after Behat's tag behavior
Configuring the test runner to use tags​
The TestSuite struct's Options field contains a Tags field which can be used to specify tags to run. This can be used to run only scenarios with a specific tag. In our current project we have configured the test runner to use the --godog.tags flag to specify tags to run.
Tagged Hooks​
- C#
- JavaScript
- Go
Some hooks are only relevant for certain scenarios. To enable you to control the execution of a hook you can supply a tag value:
[BeforeScenario("@SpecialTest")]
Hooks tagged in this way will only run if the current scenario also has this tag on it. Try it out.
Can you work out how to create a tagged hook that runs for multiple tags?
Some hooks should only run for particular scenarios. Cucumber supports tagged hooks for this:
const { Before } = require('@cucumber/cucumber')
Before({ tags: '@foo' }, function () {
// This hook runs only before scenarios tagged with @foo
})
This hook will run only for scenarios (or features) that have the @foo tag.
How would you write a hook that runs for scenarios tagged with either @web or @api?
In what order do tagged hooks execute relative to untagged hooks?
Godog does not support tagged hooks. If you need to run hooks only for certain scenarios, you will need to use conditional logic within the hook itself.