Add docs for group builders and example factory

This commit is contained in:
Michael Miller 2018-10-30 16:49:01 -06:00
parent ee4623b471
commit aea324a33b
5 changed files with 95 additions and 1 deletions

View file

@ -1,8 +1,13 @@
module Spectator::DSL module Spectator::DSL
# Creates instances of examples from a specified class.
class ExampleFactory class ExampleFactory
# Creates the factory.
# The type passed to this constructor must be a sub-type of `Example`.
def initialize(@example_type : Example.class) def initialize(@example_type : Example.class)
end end
# Constructs a new example instance and returns it.
# The `group` and `sample_valuees` are passed to `Example#initialize`.
def build(group : ExampleGroup, sample_values : Internals::SampleValues) : Example def build(group : ExampleGroup, sample_values : Internals::SampleValues) : Example
@example_type.new(group, sample_values) @example_type.new(group, sample_values)
end end

View file

@ -1,38 +1,57 @@
module Spectator::DSL module Spectator::DSL
# Base class for building all example groups.
abstract class ExampleGroupBuilder abstract class ExampleGroupBuilder
# Type alias for valid children of example groups.
# NOTE: `NestedExampleGroupBuilder` is used instead of `ExampleGroupBuilder`.
# That is because `RootExampleGroupBuilder` also inherits from this class,
# and the root example group can't be a child.
alias Child = ExampleFactory | NestedExampleGroupBuilder alias Child = ExampleFactory | NestedExampleGroupBuilder
# Factories and builders for all examples and groups.
@children = [] of Child @children = [] of Child
# Hooks added to the group so far.
@before_all_hooks = [] of -> @before_all_hooks = [] of ->
@before_each_hooks = [] of -> @before_each_hooks = [] of ->
@after_all_hooks = [] of -> @after_all_hooks = [] of ->
@after_each_hooks = [] of -> @after_each_hooks = [] of ->
@around_each_hooks = [] of Proc(Nil) -> @around_each_hooks = [] of Proc(Nil) ->
# Adds a new example factory or group builder to this group.
def add_child(child : Child) def add_child(child : Child)
@children << child @children << child
end end
# Adds a hook to run before all examples (and nested examples) in this group.
def add_before_all_hook(block : ->) : Nil def add_before_all_hook(block : ->) : Nil
@before_all_hooks << block @before_all_hooks << block
end end
# Adds a hook to run before each example (and nested example) in this group.
def add_before_each_hook(block : ->) : Nil def add_before_each_hook(block : ->) : Nil
@before_each_hooks << block @before_each_hooks << block
end end
# Adds a hook to run after all examples (and nested examples) in this group.
def add_after_all_hook(block : ->) : Nil def add_after_all_hook(block : ->) : Nil
@after_all_hooks << block @after_all_hooks << block
end end
# Adds a hook to run after each example (and nested example) in this group.
def add_after_each_hook(block : ->) : Nil def add_after_each_hook(block : ->) : Nil
@after_each_hooks << block @after_each_hooks << block
end end
# Adds a hook to run around each example (and nested example) in this group.
# The block of code will be given another proc as an argument.
# It is expected that the block will call the proc.
def add_around_each_hook(block : Proc(Nil) ->) : Nil def add_around_each_hook(block : Proc(Nil) ->) : Nil
@around_each_hooks << block @around_each_hooks << block
end 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.
private def hooks private def hooks
ExampleHooks.new( ExampleHooks.new(
@before_all_hooks, @before_all_hooks,

View file

@ -1,23 +1,63 @@
require "./nested_example_group_builder" require "./nested_example_group_builder"
module Spectator::DSL module Spectator::DSL
# Specialized example group builder for "given" groups.
# The type parameter `T` should be the type of each element in the given collection.
# This builder creates a container group with groups inside for each item in the collection.
# The hooks are only defined for the container group.
# By doing so, the hooks are defined once, are inherited, and use less memory.
class GivenExampleGroupBuilder(T) < NestedExampleGroupBuilder class GivenExampleGroupBuilder(T) < NestedExampleGroupBuilder
# Creates a new group builder.
# The value for `what` should be the text the user specified for the collection.
# The `collection` is the actual array of items to create examples for.
#
# In this code:
# ```
# given random_integers do
# # ...
# end
# ```
# The `what` value would be "random_integers"
# and the collection would contain the items returned by calling `random_integers`.
#
# The `symbol` is passed along to the sample values
# so that the example code can retrieve the current item from the collection.
# The symbol should be unique.
def initialize(what : String, @collection : Array(T), @symbol : Symbol) def initialize(what : String, @collection : Array(T), @symbol : Symbol)
super(what) super(what)
end end
# Builds the example group.
# A new `NestedExampleGroup` will be returned
# which can have instances of `Example` and `ExampleGroup` nested in it.
# 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 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).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| group.children = @collection.map do |value|
# Create a sub-group for each item in the collection.
build_sub_group(group, sample_values, value).as(ExampleComponent) build_sub_group(group, sample_values, value).as(ExampleComponent)
end end
end end
end end
# Builds a sub-group for one item in the collection.
# The `parent` should be the container group currently being built by the `#build` call.
# The `sample_values` should be the same as what was passed to the `#build` call.
# The `value` is the current item in the collection.
# The value will be added to the sample values for the sub-group,
# so it shouldn't be added prior to calling this method.
private def build_sub_group(parent : ExampleGroup, sample_values : Internals::SampleValues, value : T) : NestedExampleGroup private def build_sub_group(parent : ExampleGroup, sample_values : Internals::SampleValues, value : T) : NestedExampleGroup
sub_values = sample_values.add(@symbol, @symbol.to_s, value) # TODO: Use real name instead of symbol as string. # Add the value to sample values for this sub-group.
# TODO: Use real name instead of symbol as string.
sub_values = sample_values.add(@symbol, @symbol.to_s, value)
NestedExampleGroup.new(value.to_s, parent, ExampleHooks.empty).tap do |group| NestedExampleGroup.new(value.to_s, parent, ExampleHooks.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| group.children = @children.map do |child|
# Build the child and up-cast to prevent type errors.
child.build(group, sub_values).as(ExampleComponent) child.build(group, sub_values).as(ExampleComponent)
end end
end end

View file

@ -1,11 +1,34 @@
module Spectator::DSL module Spectator::DSL
# Standard example group builder.
# Creates groups of examples and nested groups.
class NestedExampleGroupBuilder < ExampleGroupBuilder class NestedExampleGroupBuilder < ExampleGroupBuilder
# Creates a new group builder.
# The value for `what` should be the context for the group.
#
# For example, in these samples:
# ```
# describe String do
# # ...
# context "with an empty string" do
# # ...
# end
# end
# ```
# The value would be "String" for the describe block
# and "with an empty string" for the context block.
def initialize(@what : String) def initialize(@what : String)
end end
# Builds the example group.
# A new `NestedExampleGroup` will be returned
# which can have instances of `Example` and `ExampleGroup` nested in it.
# 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 def build(parent : ExampleGroup, sample_values : Internals::SampleValues) : NestedExampleGroup
NestedExampleGroup.new(@what, parent, hooks).tap do |group| NestedExampleGroup.new(@what, parent, hooks).tap do |group|
# Set the group's children to built versions of the children from this instance.
group.children = @children.map do |child| group.children = @children.map do |child|
# Build the child and up-cast to prevent type errors.
child.build(group, sample_values).as(ExampleComponent) child.build(group, sample_values).as(ExampleComponent)
end end
end end

View file

@ -1,8 +1,15 @@
module Spectator::DSL module Spectator::DSL
# Top-level example group builder.
# There should only be one instance of this class,
# and it should be at the top of the spec "tree".
class RootExampleGroupBuilder < ExampleGroupBuilder class RootExampleGroupBuilder < ExampleGroupBuilder
# 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 def build(sample_values : Internals::SampleValues) : RootExampleGroup
RootExampleGroup.new(hooks).tap do |group| RootExampleGroup.new(hooks).tap do |group|
# Set the group's children to built versions of the children from this instance.
group.children = @children.map do |child| group.children = @children.map do |child|
# Build the child and up-cast to prevent type errors.
child.build(group, sample_values).as(ExampleComponent) child.build(group, sample_values).as(ExampleComponent)
end end
end end