Permalink
Browse files

Merge pull request #115 from ywen/features/before-after

Features/before and after hooks
  • Loading branch information...
txus committed Sep 13, 2012
2 parents 4cef70a + dfeace4 commit 7dad3e3e967f20f27696104be94885f7a856450d
View
@@ -208,6 +208,26 @@ Full hook documentation is here:
[Spinach's hook documentation on rubydoc](http://rubydoc.info/github/codegram/spinach/master/Spinach/Hooks)
+## Local Before and After Hooks
+
+Sometimes it feels awkward to add steps into feature file just because you need to do some test setup and cleanup. And it is equally awkward to add a global hooks for this purpose. For example, if you want to add a session timeout feature, to do so, you want to set the session timeout time to 1 second just for this feature, and put the normal timeout back after this feature. It doesn't make sense to add two steps in the feature file just to change the session timeout value. In this scenario, a ```before``` and ```after``` blocks are perfect for this kind of tasks. Below is an example implementation:
+
+```ruby
+class SessionTimeout < Spinach::FeatureSteps
+ attr_accessor :original_timeout_value
+ before do
+ self.original_timeout_value = session_timeout_value
+ change_session_timeout_to 1.second
+ end
+
+ after do
+ change_session_timeout_to original_timeout_value
+ end
+
+ # remaining steps
+end
+```
+
## Wanna use it with Rails 3?
Use [spinach-rails](http://github.com/codegram/spinach-rails)
@@ -0,0 +1,10 @@
+Feature: Before and after hooks
+ In order to setup and clean the test environment for a single feature
+ As a developer
+ I want to be able to use before and after hooks within the step class
+
+ Scenario: Happy path
+ Then I can verify the variable setup in the before hook
+
+ Scenario: Inter-dependency - after hook cleans up (after happy path)
+ Then I can verify the variable setup in the before hook
@@ -0,0 +1,12 @@
+Feature: Before and after hooks inheritance
+ In order to maintain the super classes' before and after code
+ As a developer
+ I want to chain the before and after blocks
+
+ Scenario: Happy path
+ Then I can see the variable setup in the super class before hook
+ And I can see the variable being overridden in the subclass
+
+ Scenario: Inter-dependency - after hook cleans up (after happy path)
+ Then I can see the variable setup in the super class before hook
+ And I can see the variable being overridden in the subclass
@@ -0,0 +1,21 @@
+class BeforeAndAfterHooks < Spinach::FeatureSteps
+ class << self
+ attr_accessor :var1
+ end
+
+ before do
+ if self.class.var1.nil?
+ self.class.var1 = :clean
+ else
+ self.class.var1 = :dirty
+ end
+ end
+
+ after do
+ self.class.var1 = nil
+ end
+
+ Then 'I can verify the variable setup in the before hook' do
+ self.class.var1.must_equal :clean
+ end
+end
@@ -0,0 +1,43 @@
+class BeforeAndAfterHooksInheritanceBase < Spinach::FeatureSteps
+ class << self
+ attr_accessor :var1
+ attr_accessor :var2
+ end
+
+ before do
+ if self.class.var2.nil?
+ self.class.var2 = :i_am_here
+ else
+ self.class.var2 = :dirty
+ end
+ if self.class.var1.nil?
+ self.class.var1 = :clean
+ else
+ self.class.var1 = :dirty
+ end
+ end
+
+ after do
+ self.class.var1 = nil
+ end
+
+end
+
+class BeforeAndAfterHooksInheritance < BeforeAndAfterHooksInheritanceBase
+ before do
+ self.class.var1 = :in_subclass
+ end
+
+ after do
+ self.class.var2 = nil
+ end
+
+ Then 'I can see the variable being overridden in the subclass' do
+ self.class.var1.must_equal :in_subclass
+ end
+
+ Then "I can see the variable setup in the super class before hook" do
+ self.class.var2.must_equal :i_am_here
+ end
+
+end
View
@@ -58,6 +58,69 @@ def step(step, &block)
alias_method :Then, :step
alias_method :And, :step
alias_method :But, :step
+
+ # Defines a before hook for each scenario. The scope is limited only to the current
+ # step class (thus the current feature).
+ #
+ # When a scenario is executed, the before each block will be run first before any steps
+ #
+ # User can define multiple before blocks throughout the class hierarchy and they are chained
+ # through the inheritance chain when executing
+ #
+ # @example
+ #
+ # class MySpinach::Base< Spinach::FeatureSteps
+ # before do
+ # @var1 = 30
+ # @var2 = 40
+ # end
+ # end
+ #
+ # class MyFeature < MySpinach::Base
+ # before do
+ # self.original_session_timeout = 1000
+ # change_session_timeout_to(1)
+ # @var2 = 50
+ # end
+ # end
+ #
+ # When running a scenario in MyFeature, @var1 is 30 and @var2 is 50
+ #
+ # @api public
+ def before(&block)
+ define_before_or_after_method_with_block(:before, &block)
+ end
+
+ # Defines a after hook for each scenario. The scope is limited only to the current
+ # step class (thus the current feature).
+ #
+ # When a scenario is executed, the after each block will be run after any steps
+ #
+ # User can define multiple after blocks throughout the class hierarchy and they are chained
+ # through the inheritance chain when executing.
+ #
+ # @example
+ #
+ # class MySpinach::Base < Spinach::FeatureSteps
+ # after do
+ # @var1 = 30
+ # @var2 = 40
+ # end
+ # end
+ #
+ # class MyFeature < MySpinach::Base
+ # after do
+ # change_session_timeout_to(original_session_timeout)
+ # @var2 = 50
+ # end
+ # end
+ #
+ # When running a scenario in MyFeature, @var1 is 30 and @var2 is 50
+ #
+ # @api public
+ def after(&block)
+ define_before_or_after_method_with_block(:after, &block)
+ end
# Sets the feature name.
#
@@ -74,6 +137,25 @@ def feature(name)
@feature_name = name
end
+ private
+
+ def before_or_after_private_method_name(location)
+ hash_value = hash
+ class_name = self.name || ""
+ class_name = class_name.gsub("::", "__").downcase
+ private_method_name = "_#{location}_each_block_#{hash.abs}_#{class_name}" #uniqueness
+ end
+
+ def define_before_or_after_method_with_block(location, &block)
+ define_method(before_or_after_private_method_name(location), &block)
+ private before_or_after_private_method_name(location)
+ private_method_name = before_or_after_private_method_name location
+ define_method "#{location}_each" do
+ super()
+ send(private_method_name)
+ end
+ end
+
end
# Instance methods to include in the host class.
@@ -26,5 +26,8 @@ def include(*args)
include_private(*args)
end
end
+
+ def before_each; end
+ def after_each; end
end
end
@@ -47,6 +47,7 @@ def run
scenario_run = false
Spinach.hooks.run_around_scenario @scenario, step_definitions do
scenario_run = true
+ step_definitions.before_each
steps.each do |step|
Spinach.hooks.run_before_step step, step_definitions
@@ -58,6 +59,7 @@ def run
Spinach.hooks.run_after_step step, step_definitions
end
+ step_definitions.after_each
end
raise "around_scenario hooks *must* yield" if !scenario_run && !@exception
Spinach.hooks.run_after_scenario @scenario, step_definitions
View
@@ -28,6 +28,114 @@
end
end
+ describe "#after" do
+ let(:super_class) {
+ Class.new do
+ attr_reader :var1
+ def after_each
+ @var1 = 30
+ @var2 = 60
+ end
+ end
+ }
+
+ let(:feature) {
+ Class.new(super_class) do
+ attr_accessor :var2
+ include Spinach::DSL
+ after do
+ self.var2 = 40
+ end
+ end
+ }
+
+ let(:object) { feature.new }
+
+ it "defines after_each method and calls the super first" do
+ object.after_each
+ object.var1.must_equal 30
+ end
+
+ it "defines after_each method and runs the block second" do
+ object.after_each
+ object.var2.must_equal 40
+ end
+
+ describe "deep inheritance" do
+ let(:sub_feature) {
+ Class.new(feature) do
+ include Spinach::DSL
+ attr_reader :var3
+
+ after do
+ @var3 = 15
+ end
+ end
+ }
+
+ let(:sub_object) { sub_feature.new }
+
+ it "works with the 3rd layer of inheritance" do
+ sub_object.after_each
+ sub_object.var2.must_equal 40
+ end
+ end
+ end
+
+ describe "#before" do
+ let(:super_class) {
+ Class.new do
+ attr_reader :var1
+ def before_each
+ @var1 = 30
+ @var2 = 60
+ end
+ end
+ }
+
+ let(:feature) {
+ Class.new(super_class) do
+ attr_accessor :var2
+ include Spinach::DSL
+ before do
+ self.var2 = 40
+ end
+ end
+ }
+
+ let(:object) { feature.new }
+
+ it "defines before_each method and calls the super first" do
+ object.before_each
+ object.var1.must_equal 30
+ end
+
+ it "defines before_each method and runs the block second" do
+ object.before_each
+ object.var2.must_equal 40
+ end
+
+ describe "deep inheritance" do
+ let(:sub_feature) {
+ Class.new(feature) do
+ include Spinach::DSL
+ attr_reader :var3
+
+ before do
+ @var3 = 15
+ end
+ end
+ }
+
+ let(:sub_object) { sub_feature.new }
+
+ it "works with the 3rd layer of inheritance" do
+ sub_object.before_each
+ sub_object.var2.must_equal 40
+ end
+ end
+ end
+
describe '#feature' do
it 'sets the name for this feature' do
@feature.feature('User salutes')
@@ -31,6 +31,14 @@
end.new
end
+ it "responds to before_each" do
+ feature.must_respond_to(:before_each)
+ end
+
+ it "responds to after_each" do
+ feature.must_respond_to(:after_each)
+ end
+
describe 'execute' do
it 'runs defined step correctly' do
feature.execute(stub(name: 'I go to the toilet'))
Oops, something went wrong.

0 comments on commit 7dad3e3

Please sign in to comment.