Reputation: 898
Is there a way to run specific code block before and after each cucumber feature with certain tag?
Since setup process is very expensive I don't want to run it before each scenario.
Upvotes: 14
Views: 26555
Reputation: 156
Mocking of BeforeFeature/ AfterFeature hooks is achievable by tagging the first scenario of the feature with a tag say @ExecuteBeforeFeature and last scenario with tag say @ExecuteAfterFeature and then writing the tagged Before and After hooks as following:
Before('@ExecuteBeforeFeature') do
#code in this method will be executed only before the first scenario or before the feature if only the first scenario is tagged for this hook.
end
After('@ExecuteAfterFeature') do
#code in this method will be executed only after the last scenario or after the feature if only the last scenario is tagged for this hook.
end
Upvotes: 1
Reputation: 1
a modification of the first answer works for me with single quotes
Before('@feature_with_expensive_setup') do
unless '@setup_is_done'
# perform expensive setup code here ..
@setup_is_done = true
end
end
Upvotes: 0
Reputation: 552
As ruby cucumber does not come supplied with the option to create hooks for before and after a feature, an ad hoc solution has been put forward.
In order to specify a hook relating to a feature, the method name must be in the following format:
before_feature_[formatted feature name] after_feature_[formatted feature name]
Where the formatted feature name is the text from the 'Feature:' line in the feature file formatted with:
(i) all characters lowercased; (ii) all spaces replaced with underscores; and, (iii) all special characters deleted
Within methods matching this convention code can be specified as with scenario hooks.
Building on the solutions by LukasMac and Gob00st I have implemented the following workarounds for my current client.
The methods go in a hooks subdirectory called AAA_special_hooks, in a special_hooks.rb file in that directory (the sole file), this is because all other things being equal the hooks will be run in the order they appear in the project structure and this way the hooks created here are run before any scenario hooks specified in other subdirectories or in the hooks base directory.
The code in the below appendix is vanilla, so far as I can see it would work for anyone.
The before hook runs on the principle of setting a global flag to ensure the hook is only run once for a feature (as per LukasMac and Gob00st). That principle has been extended to the abstract hook for two reasons, firstly to simplify the specification of hooks in general and also to have consistency with the after hook implementation.
The after hook is designed to determine whether the feature has changed since the last scenario was executed. If so the after hook will be run for the previous feature before anything happens in the current one. Clearly the vulnerability could be that the new feature has in fact been started before the after hook runs for the previous one, but I can't see how this might cause any issues. The final feature however cannot have an after hook run in this way and that is the reason for the reimplementation of that code in the at_exit method.
def get_formatted_name(feature_name)
formatted_name = feature_name.downcase.gsub(' ', '_')
%w[@ ' , . / ! " £ $ % ^ & * ( ) { } [ ] ; : # ~ ? < > \] + = - ` ¬ |].each { |char| formatted_name.gsub! char, '' }
formatted_name
end
Before do |scenario|
$completed_before_hooks ||= []
$feature_name ||= scenario.feature.name
unless $feature_name == scenario.feature.name
# run after hook for previous feature
begin
send "after_feature_#{get_formatted_name $feature_name}"
rescue NoMethodError
end
end
#run before hook for current feature if not already done
begin
formatted_name = get_formatted_name scenario.feature.name
unless $completed_before_hooks.include? formatted_name
$completed_before_hooks << formatted_name
send "before_feature_#{formatted_name}"
end
rescue NoMethodError
end
$feature_name = scenario.feature.name
end
at_exit do
# don't run final hook if error raised that was not handled
unless $! && $!.status > 1
puts 'EXECUTING FINAL AFTER HOOK... PLEASE WAIT'
begin
send "after_feature_#{get_formatted_name $feature_name}"
rescue NoMethodError
end
puts 'FINAL AFTER HOOK COMPLETED'
end
end
Upvotes: 3
Reputation: 898
Few days ago I've spoke with Matt Wynne (one of the core team member of cucumber gem) and he told me that there is no such feature in cucumber (at the time of writing this).
As a workaround he suggested to tag whole feature and use before each hook with a flag like so:
Before('@feature_with_expensive_setup') do
unless @setup_is_done
# perform expensive setup code here ..
@setup_is_done = true
end
end
Upvotes: 4
Reputation: 5947
LukasMac's answer didn't work with @ variable. Ande based on official cucumber wiki page, my example below worked and tested ok, the before hook below only execute once per feature:
Before('@my_feature_tag') do
unless $dts_test_preparation_done
# expensive setup in my case setup lots of database tables for test
$dts_test_preparation_done = true
end
end
Upvotes: 6
Reputation: 7835
The hooks for cucumber are described in this wiki page which show the before and after hooks you can have.
Taken from that page is this example:
The following example will cause scenarios tagged with @fast to fail if the execution takes longer than 0.5 seconds:
Around('@fast') do |scenario, block|
Timeout.timeout(0.5) do
block.call
end
end
Upvotes: 2