From 050251b491aad91f9cc5756a6e053babdf2d4c95 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Tue, 1 Jan 2019 17:51:55 -0700 Subject: [PATCH] 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. --- src/spectator/dsl/structure_dsl.cr | 48 ++++++++++------- src/spectator/internals/base_example_hooks.cr | 53 ------------------- 2 files changed, 30 insertions(+), 71 deletions(-) delete mode 100644 src/spectator/internals/base_example_hooks.cr diff --git a/src/spectator/dsl/structure_dsl.cr b/src/spectator/dsl/structure_dsl.cr index c81d16c..83904ed 100644 --- a/src/spectator/dsl/structure_dsl.cr +++ b/src/spectator/dsl/structure_dsl.cr @@ -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. diff --git a/src/spectator/internals/base_example_hooks.cr b/src/spectator/internals/base_example_hooks.cr deleted file mode 100644 index b02aba4..0000000 --- a/src/spectator/internals/base_example_hooks.cr +++ /dev/null @@ -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