Skip to content

Commit 1ecac46

Browse files
Add BeforeAll and AfterAll hooks (#1569)
* Add a feature for BeforeAll hook * Add BeforeAll hook * Add an AfterAll_hook feature * Add AfterAll hook * Move all hook related features into a dedicated sub-folder * Add a scenario for AfterAll to make sure it is invoked even after failed scenario * [skip ci] Update CHANGELOG.md * Draft a documentation for hooks * Add some sugar to hooks README * Adlinks to new hooks documentation in CHANGELOG.md * Do not execute BeforeAll and AfterAll hooks in dry-run mode
1 parent ce1ba5f commit 1ecac46

14 files changed

+333
-0
lines changed

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ Please visit [cucumber/CONTRIBUTING.md](https://github.com/cucumber/cucumber/blo
1414

1515
### Added
1616

17+
- New `BeforeAll` and `AfterAll` hooks
18+
19+
More information about hooks can be found in
20+
[features/docs/writing_support_code/hooks/README.md](./features/docs/writing_support_code/hooks/README.md).
21+
22+
([1569](https://github.com/cucumber/cucumber-ruby/pull/1569)
23+
[aurelien-reeves](https://github.com/aurelien-reeves))
24+
1725
- New hook: `InstallPlugin`
1826

1927
It is intended to be used to install an external plugin, like cucumber-ruby-wire.
@@ -26,6 +34,9 @@ Please visit [cucumber/CONTRIBUTING.md](https://github.com/cucumber/cucumber/blo
2634
See [cucumber-ruby-wire](https://github.com/cucumber/cucumber-ruby-wire/) for a
2735
usage example.
2836

37+
More information about hooks can be found in
38+
[features/docs/writing_support_code/hooks/README.md](./features/docs/writing_support_code/hooks/README.md).
39+
2940
([1564](https://github.com/cucumber/cucumber-ruby/pull/1564)
3041
[aurelien-reeves](https://github.com/aurelien-reeves))
3142

features/docs/cli/dry_run.feature

+31
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,34 @@ Feature: Dry Run
8484
1 step (1 undefined)
8585
8686
"""
87+
88+
Scenario: With BeforeAll and AfterAll hooks
89+
Given a file named "features/test.feature" with:
90+
"""
91+
Feature:
92+
Scenario:
93+
Given this step passes
94+
"""
95+
And the standard step definitions
96+
And a file named "features/step_definitions/support.rb" with:
97+
"""
98+
BeforeAll do
99+
raise "BeforeAll hook error has been raised"
100+
end
101+
102+
AfterAll do
103+
raise "AfterAll hook error has been raised"
104+
end
105+
"""
106+
When I run `cucumber features/test.feature --publish-quiet --dry-run`
107+
Then it should pass with exactly:
108+
"""
109+
Feature:
110+
111+
Scenario: # features/test.feature:2
112+
Given this step passes # features/step_definitions/steps.rb:1
113+
114+
1 scenario (1 skipped)
115+
1 step (1 skipped)
116+
117+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Cucumber Hooks
2+
3+
Cucumber proposes several hooks to let you specify some code to be executed at
4+
different stages of test execution, like before or after the execution of a
5+
scenario.
6+
7+
Hooks are part of your support code.
8+
9+
They are executed in the following order:
10+
11+
- [AfterConfiguration](#afterconfiguration-and-installplugin)
12+
- [InstallPlugin](#afterconfiguration-and-installplugin)
13+
- [BeforeAll](#beforeall-and-afterall)
14+
- Per scenario:
15+
- [Around](#around)
16+
- [Before](#before-and-after)
17+
- Per step:
18+
- [AfterStep](#afterstep)
19+
- [After](#before-and-after)
20+
- [AfterAll](#beforeall-and-afterall)
21+
22+
You can define as many hooks as you want. If you have several hooks of the same
23+
types - for example, several `BeforeAll` hooks - they will be all executed once.
24+
25+
Multiple hooks of the same type are executed in the order that they were defined.
26+
If you wish to control this order, use manual requires in `env.rb` - This file is
27+
loaded first - or migrate them all to one `hooks.rb` file.
28+
29+
## AfterConfiguration and InstallPlugin
30+
31+
[`AfterConfiguration`](#afterconfiguration) and [`InstallPlugin`](#installplugin)
32+
hooks are dedicated to plugins and are meant to extend Cucumber. For example,
33+
[`AfterConfiguration`](#afterconfiguration) allows you to dynamically change the
34+
configuration before the execution of the tests, and [`InstallPlugin`](#installplugin)
35+
allows to have some code that would have deeper impact on the execution.
36+
37+
### AfterConfiguration
38+
39+
**Note:** this is a legacy hook. You may consider using [`InstallPlugin`](#installplugin) instead.
40+
41+
```ruby
42+
AfterConfiguration do |configuration|
43+
# configuration is an instance of Cucumber::Configuration defined in
44+
# lib/cucumber/configuration.rb.
45+
end
46+
```
47+
48+
### InstallPlugin
49+
50+
In addition to the configuration, `InstallPlugin` also has access to some of Cucumber
51+
internals through a `RegistryWrapper`, defined in
52+
[lib/cucumber/glue/registry_wrapper.rb](../../../../lib/cucumber/glue/registry_wrapper.rb).
53+
54+
```ruby
55+
InstallPlugin do |configuration, registry|
56+
# configuration is an instance of Cucumber::Configuration defined in
57+
# lib/cucumber/configuration.rb
58+
#
59+
# registry is an instance of Cucumber::Glue::RegistryWrapper defined in
60+
# lib/cucumber/glue/registry_wrapper.rb
61+
end
62+
```
63+
64+
You can see an example in the [Cucumber Wire plugin](https://github.com/cucumber/cucumber-ruby-wire).
65+
66+
## BeforeAll and AfterAll
67+
68+
`BeforeAll` is executed once before the execution of the first scenario. `AfterAll`
69+
is executed once after the execution of the last scenario.
70+
71+
These two types of hooks have no parameters. Their purpose is to set-up and/or clean-up
72+
your environment not related to Cucumber, like a database or a browser.
73+
74+
```ruby
75+
BeforeAll do
76+
# snip
77+
end
78+
79+
AfterAll do
80+
# snip
81+
end
82+
```
83+
84+
## Around
85+
86+
**Note:** `Around` is a legacy hook and its usage is discouraged in favor of
87+
[`Before` and `After`](#before-and-after) hooks.
88+
89+
`Around` is a special hook which allows you to have a block syntax. Its original
90+
purpose was to support some databases with only block syntax for transactions.
91+
92+
```ruby
93+
Around do |scenario, block|
94+
SomeDatabase::begin_transaction do # this is just for illustration
95+
block.call
96+
end
97+
end
98+
```
99+
100+
## Before and After
101+
102+
`Before` is executed before each test case. `After` is executed after each test case.
103+
They both have the test case being executed as a parameter. Within the `After` hook,
104+
the status of the test case is also available.
105+
106+
```ruby
107+
Before do |test_case|
108+
log test_case.name
109+
end
110+
111+
After do |test_case|
112+
log test_case.failed?
113+
log test_case.status
114+
end
115+
```
116+
117+
## AfterStep
118+
119+
`AfterStep` is executed after each step of a test. If steps are not executed due
120+
to a previous failure, `AfterStep` won't be executed either.
121+
122+
```ruby
123+
AfterStep do |result, test_step|
124+
log test_step.inspect # test_step is a Cucumber::Core::Test::Step
125+
log result.inspect # result is a Cucumber::Core::Test::Result
126+
end
127+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
Feature: AfterAll Hooks
2+
3+
AfterAll hooks can be used if you have some clean-up to be done after all
4+
scenarios have been executed.
5+
6+
Scenario: A single AfterAll hook
7+
8+
An AfterAll hook will be invoked a single time after all the scenarios have
9+
been executed.
10+
11+
Given a file named "features/f.feature" with:
12+
"""
13+
Feature: AfterAll hook
14+
Scenario: #1
15+
Then the AfterAll hook has not been called yet
16+
17+
Scenario: #2
18+
Then the AfterAll hook has not been called yet
19+
"""
20+
And a file named "features/step_definitions/steps.rb" with:
21+
"""
22+
hookCalled = 0
23+
24+
AfterAll do
25+
hookCalled += 1
26+
27+
raise "AfterAll hook error has been raised"
28+
end
29+
30+
Then /^the AfterAll hook has not been called yet$/ do
31+
expect(hookCalled).to eq 0
32+
end
33+
"""
34+
When I run `cucumber features/f.feature --publish-quiet`
35+
Then it should fail with:
36+
"""
37+
Feature: AfterAll hook
38+
39+
Scenario: #1 # features/f.feature:2
40+
Then the AfterAll hook has not been called yet # features/step_definitions/steps.rb:9
41+
42+
Scenario: #2 # features/f.feature:5
43+
Then the AfterAll hook has not been called yet # features/step_definitions/steps.rb:9
44+
45+
2 scenarios (2 passed)
46+
2 steps (2 passed)
47+
"""
48+
And the output should contain:
49+
"""
50+
AfterAll hook error has been raised (RuntimeError)
51+
"""
52+
53+
Scenario: It is invoked also when scenario has failed
54+
55+
Given a file named "features/f.feature" with:
56+
"""
57+
Feature: AfterAll hook
58+
Scenario: failed
59+
Given a failed step
60+
"""
61+
And a file named "features/step_definitions/steps.rb" with:
62+
"""
63+
AfterAll do
64+
raise "AfterAll hook error has been raised"
65+
end
66+
67+
Given /^a failed step$/ do
68+
expect(0).to eq 1
69+
end
70+
"""
71+
When I run `cucumber features/f.feature --publish-quiet`
72+
Then it should fail with:
73+
"""
74+
1 scenario (1 failed)
75+
1 step (1 failed)
76+
"""
77+
And the output should contain:
78+
"""
79+
AfterAll hook error has been raised (RuntimeError)
80+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
Feature: BeforeAll Hooks
2+
3+
BeforeAll hooks can be used if you have some set-up to be done before all
4+
scenarios are executed.
5+
6+
BeforeAll hooks are not aware of your configuration. Use AfterConfiguration if
7+
you need it.
8+
9+
Scenario: A single BeforeAll hook
10+
11+
A BeforeAll hook will be invoked a single time before all the scenarios are
12+
executed.
13+
14+
Given a file named "features/f.feature" with:
15+
"""
16+
Feature: BeforeAll hook
17+
Scenario: #1
18+
Then the BeforeAll hook has been called
19+
20+
Scenario: #2
21+
Then the BeforeAll hook has been called
22+
"""
23+
And a file named "features/step_definitions/steps.rb" with:
24+
"""
25+
hookCalled = 0
26+
27+
BeforeAll do
28+
hookCalled += 1
29+
end
30+
31+
Then /^the BeforeAll hook has been called$/ do
32+
expect(hookCalled).to eq 1
33+
end
34+
"""
35+
When I run `cucumber features/f.feature`
36+
Then it should pass with:
37+
"""
38+
Feature: BeforeAll hook
39+
40+
Scenario: #1 # features/f.feature:2
41+
Then the BeforeAll hook has been called # features/step_definitions/steps.rb:7
42+
43+
Scenario: #2 # features/f.feature:5
44+
Then the BeforeAll hook has been called # features/step_definitions/steps.rb:7
45+
46+
2 scenarios (2 passed)
47+
2 steps (2 passed)
48+
49+
"""

lib/cucumber/glue/dsl.rb

+12
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,18 @@ def InstallPlugin(&proc)
118118
Dsl.register_rb_hook('install_plugin', [], proc)
119119
end
120120

121+
# Registers a proc that will run before the execution of the scenarios.
122+
# Use it for your final set-ups
123+
def BeforeAll(&proc)
124+
Dsl.register_rb_hook('before_all', [], proc)
125+
end
126+
127+
# Registers a proc that will run after the execution of the scenarios.
128+
# Use it for your final clean-ups
129+
def AfterAll(&proc)
130+
Dsl.register_rb_hook('after_all', [], proc)
131+
end
132+
121133
# Registers a new Ruby StepDefinition. This method is aliased
122134
# to <tt>Given</tt>, <tt>When</tt> and <tt>Then</tt>, and
123135
# also to the i18n translations whenever a feature of a

lib/cucumber/glue/registry_and_more.rb

+12
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,18 @@ def install_plugin(configuration, registry)
146146
end
147147
end
148148

149+
def before_all
150+
hooks[:before_all].each do |hook|
151+
hook.invoke('BeforeAll', [])
152+
end
153+
end
154+
155+
def after_all
156+
hooks[:after_all].each do |hook|
157+
hook.invoke('AfterAll', [])
158+
end
159+
end
160+
149161
def add_hook(phase, hook)
150162
hooks[phase.to_sym] << hook
151163
hook

lib/cucumber/runtime.rb

+11
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,15 @@ def run!
7575
fire_after_configuration_hook
7676
fire_install_plugin_hook
7777
install_wire_plugin
78+
fire_before_all_hook unless dry_run?
7879
# TODO: can we remove this state?
7980
self.visitor = report
8081

8182
receiver = Test::Runner.new(@configuration.event_bus)
8283
compile features, receiver, filters, @configuration.event_bus
8384
@configuration.notify :test_run_finished
85+
86+
fire_after_all_hook unless dry_run?
8487
end
8588

8689
def features_paths
@@ -119,6 +122,14 @@ def fire_install_plugin_hook #:nodoc:
119122
@support_code.fire_hook(:install_plugin, @configuration, registry_wrapper)
120123
end
121124

125+
def fire_before_all_hook #:nodoc:
126+
@support_code.fire_hook(:before_all)
127+
end
128+
129+
def fire_after_all_hook #:nodoc:
130+
@support_code.fire_hook(:after_all)
131+
end
132+
122133
require 'cucumber/core/gherkin/document'
123134
def features
124135
@features ||= feature_files.map do |path|

0 commit comments

Comments
 (0)