diff --git a/src/spectator/dsl/builder.cr b/src/spectator/dsl/builder.cr index 096ca48..d89412c 100644 --- a/src/spectator/dsl/builder.cr +++ b/src/spectator/dsl/builder.cr @@ -91,6 +91,14 @@ module Spectator::DSL current_group.add_around_each_hook(block) end + def add_pre_condition(&block : ->) : Nil + current_group.add_pre_condition(block) + end + + def add_post_condition(&block : ->) : Nil + current_group.add_post_condition(block) + end + # Builds the entire spec and returns it as a test suite. # This should be called only once after the entire spec has been defined. protected def build : TestSuite diff --git a/src/spectator/dsl/example_group_builder.cr b/src/spectator/dsl/example_group_builder.cr index 93dc958..3fc5525 100644 --- a/src/spectator/dsl/example_group_builder.cr +++ b/src/spectator/dsl/example_group_builder.cr @@ -17,6 +17,10 @@ module Spectator::DSL @after_each_hooks = [] of -> @around_each_hooks = [] of Proc(Nil) -> + # Pre and post conditions so far. + @pre_conditions = [] of -> + @post_conditions = [] of -> + # Adds a new example factory or group builder to this group. def add_child(child : Child) @children << child @@ -49,6 +53,14 @@ module Spectator::DSL @around_each_hooks << block end + def add_pre_condition(block : ->) : Nil + @pre_conditions << block + end + + def add_post_condition(block : ->) : Nil + @post_conditions << block + end + # Constructs an `ExampleHooks` instance with all the hooks defined for this group. # This method should be only when the group is being built, # otherwise some hooks may be missing. @@ -61,5 +73,9 @@ module Spectator::DSL @around_each_hooks ) end + + private def conditions + ExampleConditions.new(@pre_conditions, @post_conditions) + end end end diff --git a/src/spectator/dsl/nested_example_group_builder.cr b/src/spectator/dsl/nested_example_group_builder.cr index a05b428..dfb995c 100644 --- a/src/spectator/dsl/nested_example_group_builder.cr +++ b/src/spectator/dsl/nested_example_group_builder.cr @@ -25,7 +25,7 @@ module Spectator::DSL # The `parent` should be the group that contains this group. # The `sample_values` will be given to all of the examples (and groups) nested in this group. def build(parent : ExampleGroup, sample_values : Internals::SampleValues) : NestedExampleGroup - NestedExampleGroup.new(@what, parent, hooks).tap do |group| + NestedExampleGroup.new(@what, parent, hooks, conditions).tap do |group| # Set the group's children to built versions of the children from this instance. group.children = @children.map do |child| # Build the child and up-cast to prevent type errors. diff --git a/src/spectator/dsl/root_example_group_builder.cr b/src/spectator/dsl/root_example_group_builder.cr index 87c7097..068221b 100644 --- a/src/spectator/dsl/root_example_group_builder.cr +++ b/src/spectator/dsl/root_example_group_builder.cr @@ -6,7 +6,7 @@ module Spectator::DSL # Creates a `RootExampleGroup` which can have instances of `Example` and `ExampleGroup` nested in it. # The `sample_values` will be given to all of the examples (and groups) nested in this group. def build(sample_values : Internals::SampleValues) : RootExampleGroup - RootExampleGroup.new(hooks).tap do |group| + RootExampleGroup.new(hooks, conditions).tap do |group| # Set the group's children to built versions of the children from this instance. group.children = @children.map do |child| # Build the child and up-cast to prevent type errors. diff --git a/src/spectator/dsl/sample_example_group_builder.cr b/src/spectator/dsl/sample_example_group_builder.cr index 21431cf..c611e9e 100644 --- a/src/spectator/dsl/sample_example_group_builder.cr +++ b/src/spectator/dsl/sample_example_group_builder.cr @@ -37,7 +37,7 @@ module Spectator::DSL def build(parent : ExampleGroup, sample_values : Internals::SampleValues) : NestedExampleGroup # This creates the container for the sub-groups. # The hooks are defined here, instead of repeating for each sub-group. - NestedExampleGroup.new(@what, parent, hooks).tap do |group| + NestedExampleGroup.new(@what, parent, hooks, conditions).tap do |group| # Set the container group's children to be sub-groups for each item in the collection. group.children = @collection.map do |value| # Create a sub-group for each item in the collection. @@ -55,7 +55,7 @@ module Spectator::DSL private def build_sub_group(parent : ExampleGroup, sample_values : Internals::SampleValues, value : T) : NestedExampleGroup # Add the value to sample values for this sub-group. sub_values = sample_values.add(@symbol, @name, value) - NestedExampleGroup.new(value.to_s, parent, ExampleHooks.empty).tap do |group| + NestedExampleGroup.new(value.to_s, parent, ExampleHooks.empty, ExampleConditions.empty).tap do |group| # Set the sub-group's children to built versions of the children from this instance. group.children = @children.map do |child| # Build the child and up-cast to prevent type errors. diff --git a/src/spectator/dsl/structure_dsl.cr b/src/spectator/dsl/structure_dsl.cr index 5b9d05e..71af696 100644 --- a/src/spectator/dsl/structure_dsl.cr +++ b/src/spectator/dsl/structure_dsl.cr @@ -1182,6 +1182,30 @@ module Spectator::DSL end end + macro pre_condition(&block) + def %condition : Nil + {{block.body}} + end + + ::Spectator::DSL::Builder.add_pre_condition do + example = ::Spectator::Internals::Harness.current.example + instance = example.instance.as({{@type.id}}) + instance.%condition + end + end + + macro post_condition + def %condition : Nil + {{block.body}} + end + + ::Spectator::DSL::Builder.add_post_condition do + example = ::Spectator::Internals::Harness.current.example + instance = example.instance.as({{@type.id}}) + instance.%condition + end + end + # Creates an example, or a test case. # The `what` argument describes "what" is being tested or asserted. # The block contains the code to run the test. diff --git a/src/spectator/example_conditions.cr b/src/spectator/example_conditions.cr new file mode 100644 index 0000000..6ac6d25 --- /dev/null +++ b/src/spectator/example_conditions.cr @@ -0,0 +1,24 @@ +module Spectator + class ExampleConditions + def self.empty + new( + [] of ->, + [] of -> + ) + end + + def initialize( + @pre_conditions : Array(->), + @post_conditions : Array(->) + ) + end + + def run_pre_conditions + @pre_conditions.each &.call + end + + def run_post_conditions + @post_conditions.each &.call + end + end +end diff --git a/src/spectator/example_group.cr b/src/spectator/example_group.cr index 0f88020..be2db1d 100644 --- a/src/spectator/example_group.cr +++ b/src/spectator/example_group.cr @@ -17,7 +17,7 @@ module Spectator # Creates the example group. # The hooks are stored to be triggered later. - def initialize(@hooks : ExampleHooks) + def initialize(@hooks : ExampleHooks, @conditions : ExampleConditions) @before_all_hooks_run = false @after_all_hooks_run = false end @@ -129,6 +129,14 @@ module Spectator run_before_each_hooks end + def run_pre_conditions + @conditions.run_pre_conditions + end + + def run_post_conditions + @conditions.run_post_conditions + end + # Runs all of the `before_all` hooks. # This should run prior to any examples in the group. # The hooks will be run only once. diff --git a/src/spectator/includes.cr b/src/spectator/includes.cr index 4d4a455..beae9db 100644 --- a/src/spectator/includes.cr +++ b/src/spectator/includes.cr @@ -18,6 +18,7 @@ require "./runnable_example" require "./pending_example" require "./dummy_example" +require "./example_conditions" require "./example_hooks" require "./example_group" require "./nested_example_group" diff --git a/src/spectator/nested_example_group.cr b/src/spectator/nested_example_group.cr index ab2aae0..9e7a511 100644 --- a/src/spectator/nested_example_group.cr +++ b/src/spectator/nested_example_group.cr @@ -17,8 +17,8 @@ module Spectator # The parent's children must contain this group, # otherwise there may be unexpected behavior. # The `hooks` are stored to be triggered later. - def initialize(@what, @parent, hooks : ExampleHooks) - super(hooks) + def initialize(@what, @parent, hooks : ExampleHooks, conditions : ExampleConditions) + super(hooks, conditions) end # Runs all of the `before_all` hooks. @@ -58,6 +58,16 @@ module Spectator parent.run_after_each_hooks end + def run_pre_conditions : Nil + super + parent.run_pre_conditions + end + + def run_post_conditions : Nil + super + parent.run_post_conditions + end + # Creates a proc that runs the `around_each` hooks # in addition to a block passed to this method. # To call the block and all `around_each` hooks, diff --git a/src/spectator/runnable_example.cr b/src/spectator/runnable_example.cr index 0f27c77..de46014 100644 --- a/src/spectator/runnable_example.cr +++ b/src/spectator/runnable_example.cr @@ -70,8 +70,9 @@ module Spectator # Capture how long it takes to run the test code. result.elapsed = Time.measure do begin - # Actually run the example code. - run_instance + group.run_pre_conditions + run_instance # Actually run the example code. + group.run_post_conditions rescue ex # Catch all errors and handle them later. result.error = ex end