Skip to content

Commit

Permalink
Include shared context safely with improved before/after hooks. (#42)
Browse files Browse the repository at this point in the history
* Add before/after/around helpers.

* Make it possible to include/prepend shared modules.
  • Loading branch information
ioquatix committed Jul 22, 2024
1 parent ffdf1f3 commit f470ce3
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 6 deletions.
52 changes: 52 additions & 0 deletions lib/sus/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,57 @@ def each(&block)
end
end
end

# Include an around method to the context class, that invokes the given block before running the test.
#
# Before hooks are called in the reverse order they are defined, in other words the last defined before hook is called first.
#
# @parameter hook [Proc] The block to execute before each test.
def before(&hook)
wrapper = Module.new

wrapper.define_method(:around) do |&block|
instance_exec(&hook)
super(&block)
end

self.include(wrapper)
end

# Include an around method to the context class, that invokes the given block after running the test.
#
# After hooks are called in the order they are defined, in other words the last defined after hook is called last.
#
# @parameter hook [Proc] The block to execute after each test. An `error` argument is passed if the test failed with an exception.
def after(&hook)
wrapper = Module.new

wrapper.define_method(:around) do |&block|
error = nil

super(&block)
rescue => error
raise
ensure
instance_exec(error, &hook)
end

self.include(wrapper)
end

# Add an around hook to the context class.
#
# Around hooks are called in the reverse order they are defined.
#
# The top level `around` implementation invokes before and after hooks.
#
# @paremeter block [Proc] The block to execute around each test.
def around(&block)
wrapper = Module.new

wrapper.define_method(:around, &block)

self.include(wrapper)
end
end
end
5 changes: 5 additions & 0 deletions lib/sus/include_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@

module Sus
module Context
# Include a shared context into the current context, along with any arguments or options.
#
# @parameter shared [Sus::Shared] The shared context to include.
# @parameter arguments [Array] The arguments to pass to the shared context.
# @parameter options [Hash] The options to pass to the shared context.
def include_context(shared, *arguments, **options)
self.class_exec(*arguments, **options, &shared.block)
end
Expand Down
4 changes: 2 additions & 2 deletions lib/sus/it_behaves_like.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ def self.build(parent, shared, arguments = nil, unique: false, &block)
base.description = shared.name
base.identity = Identity.nested(parent.identity, base.description, unique: unique)
base.set_temporary_name("#{self}[#{base.description}]")

# User provided block is evaluated first, so that it can provide default behaviour for the shared context:
if block_given?
base.class_exec(*arguments, &block)
end

base.class_exec(*arguments, &shared.block)
return base
end
Expand Down
10 changes: 9 additions & 1 deletion lib/sus/shared.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ module Shared
attr_accessor :block

def self.build(name, block)
base = Class.new
base = Module.new
base.extend(Shared)
base.name = name
base.block = block

return base
end

def included(base)
base.class_exec(&self.block)
end

def prepended(base)
base.class_exec(&self.block)
end
end

def self.Shared(name, &block)
Expand Down
35 changes: 32 additions & 3 deletions test/sus/include_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,47 @@
# Released under the MIT License.
# Copyright, 2023, by Samuel Williams.

AThing = Sus::Shared("a thing") do |key, value: 42|
AContextWithArguments = Sus::Shared("a context with arguments") do |key, value: 42|
let(:a_thing) {{key => value}}
end

AContextWithHooks = Sus::Shared("a context with hooks") do
before do
events << :shared_before
end

after do
events << :shared_after
end

around do |&block|
events << :shared_around_before
super(&block)
end
end

describe Sus::Context do
with '.include_context' do
with "a shared context with an option" do
include_context AThing, :key, value: 42
include_context AContextWithArguments, :key, value: 42

it "can include a shared context" do
it "can include a shared context with arguments" do
expect(a_thing).to be == {:key => 42}
end
end

with "a shared context with arguments" do
let(:events) {Array.new}

include AContextWithHooks

before do
events << :example_before
end

it "can include a shared context" do
expect(events).to be == [:example_before, :shared_around_before, :shared_before]
end
end
end
end

0 comments on commit f470ce3

Please sign in to comment.