Skip to content
Browse files

RDoc improvements for shared examples, Example, and Procsy.

Also did some minor refactoring in order to be able to use Yard to
document internal-DSL-generated methods like `it`, `example`, and
`specify`.
  • Loading branch information...
1 parent 0d26b06 commit 43ec3a821fc37b034a0cf0c66d5025eb8805ac23 @dchelimsky dchelimsky committed May 19, 2012
View
8 lib/rspec/core/configuration.rb
@@ -538,7 +538,7 @@ def alias_example_to(new_name, *args)
#
# Example:
#
- # alias_it_should_behave_like_to(:it_has_behavior, 'has behavior:')
+ # alias_it_behaves_like_to(:it_has_behavior, 'has behavior:')
#
# allows the user to include a shared example group like:
#
@@ -553,10 +553,12 @@ def alias_example_to(new_name, *args)
# Entity
# has behavior: sortability
# # sortability examples here
- def alias_it_should_behave_like_to(new_name, report_label = '')
- RSpec::Core::ExampleGroup.alias_it_should_behave_like_to(new_name, report_label)
+ def alias_it_behaves_like_to(new_name, report_label = '')
+ RSpec::Core::ExampleGroup.alias_it_behaves_like_to(new_name, report_label)
end
+ alias_method :alias_it_should_behave_like_to, :alias_it_behaves_like_to
+
# Adds key/value pairs to the `inclusion_filter`. If the
# `treat_symbols_as_metadata_keys_with_true_values` config option is set
# to true and `args` includes any symbols that are not part of a hash,
View
66 lib/rspec/core/example.rb
@@ -2,10 +2,38 @@ module RSpec
module Core
# Wrapper for an instance of a subclass of [ExampleGroup](ExampleGroup). An
# instance of `Example` is returned by the
- # [example](ExampleGroup#example-instance_method) method available in
+ # [example](ExampleGroup#example-instance_method) method exposed to
# examples, [before](Hooks#before-instance_method) and
# [after](Hooks#after-instance_method) hooks, and yielded to
# [around](Hooks#around-instance_method) hooks.
+ #
+ # Useful for configuring logging and/or taking some action based
+ # on the state of an example's metadata.
+ #
+ # @example
+ #
+ # RSpec.configure do |config|
+ # config.before do
+ # log example.description
+ # end
+ #
+ # config.after do
+ # log example.description
+ # end
+ #
+ # config.around do |ex|
+ # log example.description
+ # ex.run
+ # end
+ # end
+ #
+ # shared_examples "auditable" do
+ # it "does something" do
+ # log "#{example.full_description}: #{auditable.inspect}"
+ # auditable.should do_something
+ # end
+ # end
+ #
# @see ExampleGroup
class Example
# @private
@@ -70,9 +98,9 @@ def example_group
alias_method :pending?, :pending
# @api private
+ # instance_evals the block passed to the constructor in the context of
+ # the instance of [ExampleGroup](../ExampleGroup).
# @param example_group_instance the instance of an ExampleGroup subclass
- # instance_evals the block submitted to the constructor in the
- # context of the instance of ExampleGroup
def run(example_group_instance, reporter)
@example_group_instance = example_group_instance
@example_group_instance.example = self
@@ -112,28 +140,48 @@ def run(example_group_instance, reporter)
finish(reporter)
end
- # @private
+ # @api private
#
# Wraps the example block in a Proc so it can invoked using `run` or
# `call` in [around](../Hooks#around-instance_method) hooks.
def self.procsy(metadata, &proc)
proc.extend(Procsy).with(metadata)
end
- # @private
+ # Used to extend a `Proc` with behavior that makes it look something like
+ # an [Example](../Example) in an [around](../Hooks#around-instance_method)
+ # hook.
+ #
+ # @note Procsy, itself, is not a public API, but we're documenting it
+ # here to document how to interact with the object yielded to an
+ # `around` hook.
+ #
+ # @example
+ #
+ # RSpec.configure do |c|
+ # c.around do |ex| # ex is a Proc extended with Procsy
+ # if ex.metadata[:key] == :some_value && some_global_condition
+ # raise "some message"
+ # end
+ # ex.run # run delegates to ex.call
+ # end
+ # end
module Procsy
+ # The `metadata` of the [Example](../Example) instance.
attr_reader :metadata
- # @private
+ # @api private
# @param [Proc]
# Adds a `run` method to the extended Proc, allowing it to be invoked
# in an [around](../Hooks#around-instance_method) hook using either
# `run` or `call`.
- def self.extended(object)
- def object.run; call; end
+ def self.extended(proc)
+ # @api public
+ # Foo bar
+ def proc.run; call; end
end
- # @private
+ # @api private
def with(metadata)
@metadata = metadata
self
View
150 lib/rspec/core/example_group.rb
@@ -1,7 +1,7 @@
module RSpec
module Core
- # ExampleGroup and Example are the main structural elements of rspec-core.
- # Consider this example:
+ # ExampleGroup and [Example](Example) are the main structural elements of
+ # rspec-core. Consider this example:
#
# describe Thing do
# it "does something" do
@@ -47,66 +47,108 @@ def self.delegate_to_metadata(*names)
alias_method :display_name, :description
# @private
alias_method :describes, :described_class
- end
-
- # @private
- def self.define_example_method(name, extra_options={})
- module_eval(<<-END_RUBY, __FILE__, __LINE__)
- def self.#{name}(desc=nil, *args, &block)
- options = build_metadata_hash_from(args)
- options.update(:pending => RSpec::Core::Pending::NOT_YET_IMPLEMENTED) unless block
- options.update(#{extra_options.inspect})
- examples << RSpec::Core::Example.new(self, desc, options, block)
- examples.last
- end
- END_RUBY
- end
-
- define_example_method :example
- define_example_method :it
- define_example_method :specify
- define_example_method :focused, :focused => true, :focus => true
- define_example_method :focus, :focused => true, :focus => true
-
- define_example_method :pending, :pending => true
- define_example_method :xexample, :pending => 'Temporarily disabled with xexample'
- define_example_method :xit, :pending => 'Temporarily disabled with xit'
- define_example_method :xspecify, :pending => 'Temporarily disabled with xspecify'
+ # @private
+ # @macro [attach] define_example_method
+ # @param [String] name
+ # @param [Hash] extra_options
+ # @param [Block] implementation
+ def self.define_example_method(name, extra_options={})
+ module_eval(<<-END_RUBY, __FILE__, __LINE__)
+ def #{name}(desc=nil, *args, &block)
+ options = build_metadata_hash_from(args)
+ options.update(:pending => RSpec::Core::Pending::NOT_YET_IMPLEMENTED) unless block
+ options.update(#{extra_options.inspect})
+ examples << RSpec::Core::Example.new(self, desc, options, block)
+ examples.last
+ end
+ END_RUBY
+ end
- class << self
- alias_method :alias_example_to, :define_example_method
- end
+ # Defines an example within a group.
+ define_example_method :example
+ # Defines an example within a group.
+ #
+ # @see ExampleGroup::example
+ define_example_method :it
+ # Defines an example within a group.
+ # This is here primarily for backward compatibility with early versions
+ # of RSpec which used `context` and `specify` instead of `describe` and
+ # `it`.
+ define_example_method :specify
+
+ # Shortcut to define an example with `:focus` => true
+ define_example_method :focus, :focused => true, :focus => true
+ # Shortcut to define an example with `:focus` => true
+ define_example_method :focused, :focused => true, :focus => true
+
+ # Shortcut to define an example with :pending => true
+ define_example_method :pending, :pending => true
+ # Shortcut to define an example with :pending => 'Temporarily disabled with xexample'
+ define_example_method :xexample, :pending => 'Temporarily disabled with xexample'
+ # Shortcut to define an example with :pending => 'Temporarily disabled with xit'
+ define_example_method :xit, :pending => 'Temporarily disabled with xit'
+ # Shortcut to define an example with :pending => 'Temporarily disabled with xspecify'
+ define_example_method :xspecify, :pending => 'Temporarily disabled with xspecify'
+
+ # Works like `alias_method :name, :example` with the added benefit of
+ # assigning default metadata to the generated example.
+ #
+ # @note Use with caution. This extends the language used in your
+ # specs, but does not add any additional documentation. We use this
+ # in rspec to define methods like `focus` and `xit`, but we also add
+ # docs for those methods.
+ def alias_example_to name, extra={}
+ (class << self; self; end).define_example_method name, extra
+ end
- # @private
- def self.define_nested_shared_group_method(new_name, report_label=nil)
- module_eval(<<-END_RUBY, __FILE__, __LINE__)
- def self.#{new_name}(name, *args, &customization_block)
- group = describe("#{report_label || "it should behave like"} \#{name}") do
- find_and_eval_shared("examples", name, *args, &customization_block)
+ # @private
+ # @macro [attach] define_nested_shared_group_method
+ #
+ # @see SharedExampleGroup
+ def self.define_nested_shared_group_method(new_name, report_label=nil)
+ module_eval(<<-END_RUBY, __FILE__, __LINE__)
+ def #{new_name}(name, *args, &customization_block)
+ group = describe("#{report_label || "it should behave like"} \#{name}") do
+ find_and_eval_shared("examples", name, *args, &customization_block)
+ end
+ group.metadata[:shared_group_name] = name
+ group
end
- group.metadata[:shared_group_name] = name
- group
- end
- END_RUBY
- end
-
- define_nested_shared_group_method :it_should_behave_like
+ END_RUBY
+ end
- class << self
- alias_method :alias_it_should_behave_like_to, :define_nested_shared_group_method
+ # Generates a nested example group and includes the shared content
+ # mapped to `name` in the nested group.
+ define_nested_shared_group_method :it_behaves_like, "behaves like"
+ # Generates a nested example group and includes the shared content
+ # mapped to `name` in the nested group.
+ define_nested_shared_group_method :it_should_behave_like
+
+ # Works like `alias_method :name, :it_behaves_like` with the added
+ # benefit of assigning default metadata to the generated example.
+ #
+ # @note Use with caution. This extends the language used in your
+ # specs, but does not add any additional documentation. We use this
+ # in rspec to define `it_should_behave_like` (for backward
+ # compatibility), but we also add docs for that method.
+ def alias_it_behaves_like_to name, *args, &block
+ (class << self; self; end).define_nested_shared_group_method name, *args, &block
+ end
end
- alias_it_should_behave_like_to :it_behaves_like, "behaves like"
-
- # Includes shared content declared with `name`.
+ # Includes shared content mapped to `name` directly in the group in which
+ # it is declared. Unlike `it_behaves_like`, this does not create a nested
+ # example group, nor does it accept a block.
#
# @see SharedExampleGroup
def self.include_context(name, *args)
block_given? ? block_not_supported("context") : find_and_eval_shared("context", name, *args)
end
- # Includes shared content declared with `name`.
+ # Includes shared content mapped to `name` directly in the group in which
+ # it is declared. Unlike `it_behaves_like`, this does not create a nested
+ # example group, nor does it accept a block.
#
# @see SharedExampleGroup
def self.include_examples(name, *args)
@@ -121,7 +163,7 @@ def self.block_not_supported(label)
# @private
def self.find_and_eval_shared(label, name, *args, &customization_block)
raise ArgumentError, "Could not find shared #{label} #{name.inspect}" unless
- shared_block = world.shared_example_groups[name]
+ shared_block = world.shared_example_groups[name]
module_eval_with_args(*args, &shared_block)
module_eval(&customization_block) if customization_block
@@ -314,11 +356,11 @@ def self.run_after_all_hooks(example_group_instance)
# TODO: come up with a better solution for this.
RSpec.configuration.reporter.message <<-EOS
-An error occurred in an after(:all) hook.
- #{e.class}: #{e.message}
- occurred at #{e.backtrace.first}
+ An error occurred in an after(:all) hook.
+ #{e.class}: #{e.message}
+ occurred at #{e.backtrace.first}
- EOS
+ EOS
end
end
View
52 lib/rspec/core/shared_example_group.rb
@@ -1,27 +1,46 @@
module RSpec
module Core
module SharedExampleGroup
-
# @overload shared_examples(name, &block)
# @overload shared_examples(name, tags, &block)
#
- # Creates and stores (but does not evaluate) the block.
+ # Wraps the `block` in a module which can then be included in example
+ # groups using `include_examples`, `include_context`, or
+ # `it_behaves_like`.
+ #
+ # @param [String] name to match when looking up this shared group
+ # @param block to be eval'd in a nested example group generated by `it_behaves_like`
+ #
+ # @example
+ #
+ # shared_examples "auditable" do
+ # it "stores an audit record on save!" do
+ # lambda { auditable.save! }.should change(Audit, :count).by(1)
+ # end
+ # end
+ #
+ # class Account do
+ # it_behaves_like "auditable" do
+ # def auditable; Account.new; end
+ # end
+ # end
#
+ # @see ExampleGroup.it_behaves_like
# @see ExampleGroup.include_examples
# @see ExampleGroup.include_context
- def shared_examples(*args, &block)
- if [String, Symbol, Module].any? {|cls| cls === args.first }
- object = args.shift
- ensure_shared_example_group_name_not_taken(object)
- RSpec.world.shared_example_groups[object] = block
+ def shared_examples *args, &block
+ if key? args.first
+ key = args.shift
+ raise_key_taken key if key_taken? key
+ RSpec.world.shared_example_groups[key] = block
end
unless args.empty?
mod = Module.new
- (class << mod; self; end).send(:define_method, :extended) do |host|
- host.class_eval(&block)
+ (class << mod; self; end).send :define_method, :extended do |host|
+ host.class_eval &block
end
- RSpec.configuration.extend(mod, *args)
+ RSpec.configuration.extend mod, *args
end
end
@@ -55,16 +74,21 @@ def self.included(kls)
private
+ def key? candidate
+ [String, Symbol, Module].any? { |cls| cls === candidate }
+ end
+
def raise_name_error
raise NameError, "The first argument (#{name}) to share_as must be a legal name for a constant not already in use."
end
- def ensure_shared_example_group_name_not_taken(name)
- if RSpec.world.shared_example_groups.has_key?(name)
- raise ArgumentError.new("Shared example group '#{name}' already exists")
- end
+ def raise_key_taken key
+ raise ArgumentError, "Shared example group '#{key}' already exists"
end
+ def key_taken? key
+ RSpec.world.shared_example_groups.has_key?(key)
+ end
end
end
end
View
2 spec/spec_helper.rb
@@ -68,7 +68,7 @@ def in_editor?
RSpec.configure do |c|
# structural
- c.alias_it_should_behave_like_to 'it_has_behavior'
+ c.alias_it_behaves_like_to 'it_has_behavior'
c.around {|example| sandboxed { example.run }}
c.include(RSpecHelpers)
c.include Aruba::Api, :example_group => {

0 comments on commit 43ec3a8

Please sign in to comment.
Something went wrong with that request. Please try again.