Cucumber before and after feature hooks
Usage info
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.
Technical info
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.
Appendix - special_hooks.rb code
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