Skip to content

Commit

Permalink
Refactor and document hooking mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
josepjaume committed Oct 14, 2011
1 parent 2c3f1d0 commit d407be0
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 82 deletions.
1 change: 1 addition & 0 deletions lib/spinach.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require_relative 'spinach/version'
require_relative 'spinach/config'
require_relative 'spinach/hookable'
require_relative 'spinach/hooks'
require_relative 'spinach/support'
require_relative 'spinach/exceptions'
Expand Down
61 changes: 61 additions & 0 deletions lib/spinach/hookable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
module Spinach
module Hookable

def self.included(base)
base.class_eval do
extend ClassMethods
include InstanceMethods
end
end

module ClassMethods
# Adds a new hook to this class. Every hook defines two methods used to
# add new callbacks and to run them passing a bunch of parameters.
#
# @example
# class
def define_hook(hook)
define_method hook do |&block|
add_hook(hook, &block)
end
define_method "run_#{hook}" do |*args|
run_hook(hook, *args)
end
end
end

module InstanceMethods
attr_writer :hooks

def hooks
@hooks ||= {}
end

# Resets all this class' hooks to a pristine state
def reset
self.hooks = {}
end

# Runs a particular hook
#
# @param [String] name
# the hook's name
#
# @param [
def run_hook(name, *args)
if callbacks = hooks[name.to_sym]
callbacks.each{ |c| c.call(*args) }
end
end

def hooks_for(name)
hooks[name.to_sym] || []
end

def add_hook(name, &block)
hooks[name.to_sym] ||= []
hooks[name.to_sym] << block
end
end
end
end
161 changes: 129 additions & 32 deletions lib/spinach/hooks.rb
Original file line number Diff line number Diff line change
@@ -1,52 +1,149 @@
require_relative 'hookable'

module Spinach
# The hooks class is a subscription/notification mechanism that allows you to
# hook into signals provided by the runner and perform certain actions on
# it.
#
# @example
# Spinach.hooks.before_run do
# # Runs before the entire spinach execution
# end
#
# Spinach.hooks.before_scenario do |scenario_data|
# # Runs before every scenario and passes a hash of the parsed scenario
# # data to the block as argument
# end
#
# Spinach.hooks.on_failed_step do |step_data|
# # Runs before every failed stepand passes a hash of the parsed step
# # data to the block as argument
# end
class Hooks
include Hookable

def self.define_hook(hook)
define_method hook do |&block|
add_hook(hook, &block)
end
define_method "run_#{hook}" do |*args|
run_hook(hook, *args)
end
end

# Runs before the entire spinach run
#
# @example
# Spinach.before_run do
# # Whatever
# end
define_hook :before_run

# Runs after the entire spinach run
#
# @example
# Spinach.after_run do |status|
# # status is true when the run is successful, false otherwise
# end
define_hook :after_run

# Runs before every feature,
#
# @example
# Spinach.before_feature do |feature_data|
# # feature_data is a hash of the parsed feature data
# end
define_hook :before_feature

# Runs after every feature
#
# @example
# Spinach.after_feature do |feature_data|
# # feature_data is a hash of the parsed feature data
# end
define_hook :after_feature

# Runs when an undefined feature is found
#
# @example
# Spinach.on_undefined_feature do |feature_data, exception|
# # feature_data is a hash of the parsed feature data
# # exception contains the thrown exception
# end
define_hook :on_undefined_feature

# Runs before every scenario
#
# @example
# Spinach.before_scenario do |scenario_data|
# # feature_data is a hash of the parsed scenario data
# end
define_hook :before_scenario

# Runs after every scenario
#
# @example
# Spinach.after_scenario do |scenario_data|
# # feature_data is a hash of the parsed scenario data
# end
define_hook :after_scenario

# Runs before every step execution
#
# @example
# Spinach.before_step do |step_data|
# # step_data contains a hash with this step's data
# end
define_hook :before_step

# Runs after every step execution
#
# @example
# Spinach.before_step do |step_data|
# # step_data contains a hash with this step's data
# end
define_hook :after_step
define_hook :on_successful_step
define_hook :on_failed_step
define_hook :on_error_step
define_hook :on_undefined_step
define_hook :on_skipped_step

def initialize
@hooks = {}
end
# Runs after every successful step execution
#
# @example
# Spinach.on_successful_step do |step_data, location|
# # step_data contains a hash with this step's data
# # step_location contains a string indication this step definition's
# # location
# end
define_hook :on_successful_step

def reset
@hooks = {}
end
# Runs after every failed step execution
#
# @example
# Spinach.on_failed_step do |step_data, location|
# # step_data contains a hash with this step's data
# # step_location contains a string indication this step definition's
# # location
# end
define_hook :on_failed_step

def run_hook(name, *args)
if callbacks = @hooks[name.to_sym]
callbacks.each{ |c| c.call(*args) }
end
end
# Runs after every step execution that raises an exception
#
# @example
# Spinach.on_error_step do |step_data, location|
# # step_data contains a hash with this step's data
# # step_location contains a string indication this step definition's
# # location
# end
define_hook :on_error_step

def hooks_for(name)
@hooks[name.to_sym] || []
end
# Runs every time a step which is not defined is called
#
# @example
# Spinach.on_undefined_step do |step_data, location|
# # step_data contains a hash with this step's data
# # step_location contains a string indication this step definition's
# # location
# end
define_hook :on_undefined_step

def add_hook(name, &block)
@hooks[name.to_sym] ||= []
@hooks[name.to_sym] << block
end
# Runs every time a step is skipped because there has been an unsuccessful
# one just before.
#
# @example
# Spinach.on_undefined_step do |step_data|
# # step_data contains a hash with this step's data
# end
define_hook :on_skipped_step

end

end
60 changes: 60 additions & 0 deletions test/spinach/hookable_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require_relative '../test_helper'

describe Spinach::Hookable do
subject do
Class.new do
include Spinach::Hookable
end.new
end

describe ".define_hook" do
it "defines a new hook" do
subject.class.define_hook :before_save
subject.must_respond_to :before_save
end
end

describe "hooking mechanism" do
describe "without params" do
it "adds a new hook to the queue" do
subject.add_hook(:before_save) do
end
(subject.hooks_for(:before_save).empty?).must_equal false
end

it "allows to run a hook" do
arbitrary_variable = false
subject.add_hook(:before_save) do
arbitrary_variable = true
end
subject.run_hook(:before_save)
arbitrary_variable.must_equal true
end
end

describe "with params" do
it "adds a new hook to the queue" do
subject.add_hook(:before_save) do |var1, var2|
end
(subject.hooks_for(:before_save).empty?).must_equal false
end

it "allows to run a hook" do
array = []
subject.add_hook(:before_save) do |var1, var2|
array << var1
array << var2
end
subject.run_hook(:before_save, 1, 2)
array.must_equal [1, 2]
end
end

describe "#reset_hooks" do
it "resets the hooks to a pristine state" do
subject.add_hook(:before_save)
end
end
end

end
50 changes: 0 additions & 50 deletions test/spinach/hooks_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,56 +5,6 @@
Spinach::Hooks.new
end

describe ".define_hook" do
it "defines a new hook" do
subject.class.define_hook :before_save
subject.must_respond_to :before_save
end
end

describe "hooking mechanism" do
describe "without params" do
it "adds a new hook to the queue" do
subject.add_hook(:before_save) do
end
(subject.hooks_for(:before_save).empty?).must_equal false
end

it "allows to run a hook" do
arbitrary_variable = false
subject.add_hook(:before_save) do
arbitrary_variable = true
end
subject.run_hook(:before_save)
arbitrary_variable.must_equal true
end
end

describe "with params" do
it "adds a new hook to the queue" do
subject.add_hook(:before_save) do |var1, var2|
end
(subject.hooks_for(:before_save).empty?).must_equal false
end

it "allows to run a hook" do
array = []
subject.add_hook(:before_save) do |var1, var2|
array << var1
array << var2
end
subject.run_before_save(1, 2)
array.must_equal [1, 2]
end
end

describe "#reset_hooks" do
it "resets the hooks to a pristine state" do
subject.add_hook(:before_save)
end
end
end

describe "hooks" do
%w{before_run after_run before_feature after_feature on_undefined_feature
before_scenario after_scenario before_step after_step on_successful_step
Expand Down

0 comments on commit d407be0

Please sign in to comment.