before_each and after_each hooks now run in example scope

The entire example hooks class hierarchy is no longer needed.
This is an easier implementation than originally planned.
However, this is probably slower than the other method because each hook 
has to cast and check the example instance.
This commit is contained in:
Michael Miller 2019-01-01 17:51:55 -07:00
parent aee3ead578
commit 050251b491
2 changed files with 30 additions and 71 deletions

View file

@ -662,16 +662,11 @@ module Spectator::DSL
# end
# ```
#
# Currently, the hook cannot use values and methods in the group like examples can.
# This is a planned feature.
# The hook can use values and methods in the group like examples can.
# It is called in the same scope as the example code.
# ```
# let(array) { [1, 2, 3] }
# before_each { array << 4 } # *DOES NOT WORK YET!*
# ```
#
# This could also be used to verify pre-conditions:
# ```
# before_each { is_expected.to_not be_nil } # *DOES NOT WORK YET!*
# before_each { array << 4 }
# ```
#
# If multiple `#before_each` blocks are specified,
@ -698,7 +693,18 @@ module Spectator::DSL
#
# See also: `#before_all`, `#after_all`, `#after_each`, and `#around_each`.
macro before_each(&block)
::Spectator::DSL::Builder.add_before_each_hook {{block}}
# Before each hook.
# Defined as a method so that it can access the same scope as the example code.
def %hook : Nil
{{block.body}}
end
::Spectator::DSL::Builder.add_before_each_hook do
# Get the wrapper instance and cast to current group type.
example = ::Spectator::Internals::Harness.current.example
instance = example.instance.as({{@type.id}})
instance.%hook
end
end
# Creates a hook that will run following all examples in the group.
@ -767,16 +773,11 @@ module Spectator::DSL
# end
# ```
#
# Currently, the hook cannot use values and methods in the group like examples can.
# This is a planned feature.
# The hook can use values and methods in the group like examples can.
# It is called in the same scope as the example code.
# ```
# let(array) { [1, 2, 3] }
# after_each { array << 4 } # *DOES NOT WORK YET!*
# ```
#
# This could also be used to verify post-conditions:
# ```
# after_each { is_expected.to_not be_nil } # *DOES NOT WORK YET!*
# after_each { array << 4 }
# ```
#
# If multiple `#after_each` blocks are specified,
@ -803,7 +804,18 @@ module Spectator::DSL
#
# See also: `#before_all`, `#before_each`, `#after_all`, and `#around_each`.
macro after_each(&block)
::Spectator::DSL::Builder.add_after_each_hook {{block}}
# After each hook.
# Defined as a method so that it can access the same scope as the example code.
def %hook : Nil
{{block.body}}
end
::Spectator::DSL::Builder.add_after_each_hook do
# Get the wrapper instance and cast to current group type.
example = ::Spectator::Internals::Harness.current.example
instance = example.instance.as({{@type.id}})
instance.%hook
end
end
# Creates a hook that will run for every example in the group.

View file

@ -1,53 +0,0 @@
module Spectator::Internals
# Abstract base class for all example hooks.
# This provides an untyped base for every generic type of `ExampleHooks`.
#
# A hook is defined as a `Proc` instance.
# This class stores any number of hooks for before, after, and "around" an example.
# The before and after hooks run before and after an example (obviously).
# These hooks are passed the `Example` instance of the example being run.
#
# The around hook is special, in that it does both.
# It is started before the example runs and finishes after the example has ran.
# This is suited for methods that use block syntax to wrap some action.
# For instance: `File#open`.
# A `Proc` instance is given to each around hook,
# and the hook is expected to call that proc.
# If it doesn't then the example won't run, among other things.
abstract class BaseExampleHooks
# Creates a new set of hooks for examples.
# An array of `Proc` instances for each type of hook must be specified.
def initialize(@before = [] of Example ->, @after = [] of Example ->, @around = [] of Example ->)
end
# Runs all of the before hooks.
# The `example` argument should be the example about to be run.
abstract def run_before(example : Example)
# Runs all of the after hooks.
# The `example` argument should be the example that just ran.
abstract def run_after(example : Example)
# Creates a `Proc` that invokes all of the around hooks and a block of code.
# Invoking the returned proc will call each around hook
# and finally the block of code provided to this method.
def wrap_around(&block : ->)
# If there's no around hooks,
# the returned proc will just be the block.
wrapper = block
# Must wrap in reverse order,
# so that the first hooks are the outermost and run first.
@around.reverse_each do |hook|
wrapper = wrap_proc(hook, wrapper)
end
wrapper
end
# Utility method for wrapping one proc with another.
private def wrap_proc(inner : Proc(Nil) ->, wrapper : ->)
->{ inner.call(wrapper) }
end
end
end