class RSpec::Core::ExampleGroup
ExampleGroup and {Example} are the main structural elements of rspec-core. Consider this example:
describe Thing do it "does something" do end end
The object returned by `describe Thing` is a subclass of ExampleGroup. The object returned by `it “does something”` is an instance of Example, which serves as a wrapper for an instance of the ExampleGroup in which it is declared.
Example group bodies (e.g. `describe` or `context` blocks) are evaluated in the context of a new subclass of ExampleGroup. Individual examples are evaluated in the context of an instance of the specific ExampleGroup subclass to which they belong.
Besides the class methods defined here, there are other interesting macros defined in {Hooks}, {MemoizedHelpers::ClassMethods} and {SharedExampleGroup}. There are additional instance methods available to your examples defined in {MemoizedHelpers} and {Pending}.
Constants
- INSTANCE_VARIABLE_TO_IGNORE
:nocov: @private
- WrongScopeError
Raised when an RSpec API is called in the wrong scope, such as `before` being called from within an example rather than from within an example group block.
Public Class Methods
@private
# File lib/rspec/core/example_group.rb, line 456 def self.before_context_ivars @before_context_ivars ||= {} end
@private
# File lib/rspec/core/example_group.rb, line 416 def self.children @children ||= [] end
@private
# File lib/rspec/core/example_group.rb, line 583 def self.declaration_line_numbers @declaration_line_numbers ||= [metadata[:line_number]] + examples.map { |e| e.metadata[:line_number] } + FlatMap.flat_map(children, &:declaration_line_numbers) end
@private @macro [attach] ::define_example_group_method
@!scope class @overload $1 @overload $1(&example_group_definition) @param example_group_definition [Block] The definition of the example group. @overload $1(doc_string, *metadata_keys, metadata={}, &example_implementation) @param doc_string [String] The group's doc string. @param metadata [Hash] Metadata for the group. @param metadata_keys [Array<Symbol>] Metadata tags for the group. Will be transformed into hash entries with `true` values. @param example_group_definition [Block] The definition of the example group. Generates a subclass of this example group which inherits everything except the examples themselves. @example RSpec.describe "something" do # << This describe method is defined in # << RSpec::Core::DSL, included in the # << global namespace (optional) before do do_something_before end let(:thing) { Thing.new } $1 "attribute (of something)" do # examples in the group get the before hook # declared above, and can access `thing` end end
@see DSL#describe
# File lib/rspec/core/example_group.rb, line 232 def self.define_example_group_method(name, metadata={}) idempotently_define_singleton_method(name) do |*args, &example_group_block| thread_data = RSpec::Support.thread_local_data top_level = self == ExampleGroup if top_level if thread_data[:in_example_group] raise "Creating an isolated context from within a context is " "not allowed. Change `RSpec.#{name}` to `#{name}` or " "move this to a top-level scope." end thread_data[:in_example_group] = true end begin description = args.shift combined_metadata = metadata.dup combined_metadata.merge!(args.pop) if args.last.is_a? Hash args << combined_metadata subclass(self, description, args, &example_group_block).tap do |child| children << child end ensure thread_data.delete(:in_example_group) if top_level end end RSpec::Core::DSL.expose_example_group_alias(name) end
@private @macro [attach] ::define_example_method
@!scope class @overload $1 @overload $1(&example_implementation) @param example_implementation [Block] The implementation of the example. @overload $1(doc_string, *metadata_keys, metadata={}) @param doc_string [String] The example's doc string. @param metadata [Hash] Metadata for the example. @param metadata_keys [Array<Symbol>] Metadata tags for the example. Will be transformed into hash entries with `true` values. @overload $1(doc_string, *metadata_keys, metadata={}, &example_implementation) @param doc_string [String] The example's doc string. @param metadata [Hash] Metadata for the example. @param metadata_keys [Array<Symbol>] Metadata tags for the example. Will be transformed into hash entries with `true` values. @param example_implementation [Block] The implementation of the example. @yield [Example] the example object @example $1 do end $1 "does something" do end $1 "does something", :slow, :uses_js do end $1 "does something", :with => 'additional metadata' do end $1 "does something" do |ex| # ex is the Example object that contains metadata about the example end
# File lib/rspec/core/example_group.rb, line 137 def self.define_example_method(name, extra_options={}) idempotently_define_singleton_method(name) do |*all_args, &block| desc, *args = *all_args options = Metadata.build_hash_from(args) options.update(:skip => RSpec::Core::Pending::NOT_YET_IMPLEMENTED) unless block options.update(extra_options) example = RSpec::Core::Example.new(self, desc, options, block) examples << example example end end
@private
# File lib/rspec/core/example_group.rb, line 72 def self.delegate_to_metadata(*names) names.each do |name| idempotently_define_singleton_method(name) { metadata.fetch(name) } end end
@private
# File lib/rspec/core/example_group.rb, line 410 def self.descendant_filtered_examples @descendant_filtered_examples ||= filtered_examples + FlatMap.flat_map(children, &:descendant_filtered_examples) end
@private
# File lib/rspec/core/example_group.rb, line 430 def self.descendants @_descendants ||= [self] + FlatMap.flat_map(children, &:descendants) end
@return [String] the current example group description
# File lib/rspec/core/example_group.rb, line 81 def self.description description = metadata[:description] RSpec.configuration.format_docstrings_block.call(description) end
@private
# File lib/rspec/core/example_group.rb, line 616 def self.each_instance_variable_for_example(group) group.instance_variables.each do |ivar| yield ivar unless ivar == INSTANCE_VARIABLE_TO_IGNORE end end
@private
# File lib/rspec/core/example_group.rb, line 445 def self.ensure_example_groups_are_configured unless defined?(@@example_groups_configured) RSpec.configuration.configure_mock_framework RSpec.configuration.configure_expectation_framework # rubocop:disable Style/ClassVars @@example_groups_configured = true # rubocop:enable Style/ClassVars end end
@private
# File lib/rspec/core/example_group.rb, line 400 def self.examples @examples ||= [] end
@private
# File lib/rspec/core/example_group.rb, line 578 def self.fail_fast? RSpec.configuration.fail_fast? end
@private
# File lib/rspec/core/example_group.rb, line 405 def self.filtered_examples RSpec.world.filtered_examples[self] end
@private
# File lib/rspec/core/example_group.rb, line 566 def self.for_filtered_examples(reporter, &block) filtered_examples.each(&block) children.each do |child| reporter.example_group_started(child) child.for_filtered_examples(reporter, &block) reporter.example_group_finished(child) end false end
@return [String] the unique id of this example group. Pass
this at the command line to re-run this exact example group.
# File lib/rspec/core/example_group.rb, line 591 def self.id Metadata.id_from(metadata) end
@private
# File lib/rspec/core/example_group.rb, line 36 def self.idempotently_define_singleton_method(name, &definition) (class << self; self; end).module_exec do remove_method(name) if method_defined?(name) && instance_method(name).owner == self define_method(name, &definition) end end
Includes shared content mapped to `name` directly in the group in which it is declared, as opposed to `it_behaves_like`, which creates a nested group. If given a block, that block is also eval'd in the current context.
@see SharedExampleGroup
# File lib/rspec/core/example_group.rb, line 329 def self.include_context(name, *args, &block) find_and_eval_shared("context", name, caller.first, *args, &block) end
Includes shared content mapped to `name` directly in the group in which it is declared, as opposed to `it_behaves_like`, which creates a nested group. If given a block, that block is also eval'd in the current context.
@see SharedExampleGroup
# File lib/rspec/core/example_group.rb, line 339 def self.include_examples(name, *args, &block) find_and_eval_shared("examples", name, caller.first, *args, &block) end
The [Metadata](Metadata) object associated with this group. @see Metadata
# File lib/rspec/core/example_group.rb, line 47 def self.metadata @metadata ||= nil end
# File lib/rspec/core/example_group.rb, line 622 def initialize(inspect_output=nil) @__inspect_output = inspect_output || '(no description provided)' super() # no args get passed end
@private
# File lib/rspec/core/example_group.rb, line 421 def self.next_runnable_index_for(file) if self == ExampleGroup RSpec.world.num_example_groups_defined_in(file) else children.count + examples.count end + 1 end
@private
# File lib/rspec/core/example_group.rb, line 538 def self.ordering_strategy order = metadata.fetch(:order, :global) registry = RSpec.configuration.ordering_registry registry.fetch(order) do warn " |WARNING: Ignoring unknown ordering specified using `:order => #{order.inspect}` metadata. | Falling back to configured global ordering. | Unrecognized ordering specified at: #{location} ".gsub(/^ +\|/, '') registry.fetch(:global) end end
@private
# File lib/rspec/core/example_group.rb, line 435 def self.parent_groups @parent_groups ||= ancestors.select { |a| a < RSpec::Core::ExampleGroup } end
Runs all the examples in this group.
# File lib/rspec/core/example_group.rb, line 514 def self.run(reporter=RSpec::Core::NullReporter) return if RSpec.world.wants_to_quit reporter.example_group_started(self) should_run_context_hooks = descendant_filtered_examples.any? begin run_before_context_hooks(new('before(:context) hook')) if should_run_context_hooks result_for_this_group = run_examples(reporter) results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all? result_for_this_group && results_for_descendants rescue Pending::SkipDeclaredInExample => ex for_filtered_examples(reporter) { |example| example.skip_with_exception(reporter, ex) } true rescue Exception => ex RSpec.world.wants_to_quit = true if fail_fast? for_filtered_examples(reporter) { |example| example.fail_with_exception(reporter, ex) } false ensure run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks reporter.example_group_finished(self) end end
@private
# File lib/rspec/core/example_group.rb, line 503 def self.run_after_context_hooks(example_group_instance) set_ivars(example_group_instance, before_context_ivars) ContextHookMemoized::After.isolate_for_context_hook(example_group_instance) do hooks.run(:after, :context, example_group_instance) end ensure before_context_ivars.clear end
@private
# File lib/rspec/core/example_group.rb, line 468 def self.run_before_context_hooks(example_group_instance) set_ivars(example_group_instance, superclass_before_context_ivars) ContextHookMemoized::Before.isolate_for_context_hook(example_group_instance) do hooks.run(:before, :context, example_group_instance) end ensure store_before_context_ivars(example_group_instance) end
@private
# File lib/rspec/core/example_group.rb, line 554 def self.run_examples(reporter) ordering_strategy.order(filtered_examples).map do |example| next if RSpec.world.wants_to_quit instance = new(example.inspect_output) set_ivars(instance, before_context_ivars) succeeded = example.run(instance, reporter) RSpec.world.wants_to_quit = true if fail_fast? && !succeeded succeeded end.all? end
@private
# File lib/rspec/core/example_group.rb, line 375 def self.set_it_up(description, *args, &example_group_block) # Ruby 1.9 has a bug that can lead to infinite recursion and a # SystemStackError if you include a module in a superclass after # including it in a subclass: https://gist.github.com/845896 # To prevent this, we must include any modules in # RSpec::Core::ExampleGroup before users create example groups and have # a chance to include the same module in a subclass of # RSpec::Core::ExampleGroup. So we need to configure example groups # here. ensure_example_groups_are_configured user_metadata = Metadata.build_hash_from(args) @metadata = Metadata::ExampleGroupHash.create( superclass_metadata, user_metadata, superclass.method(:next_runnable_index_for), description, *args, &example_group_block ) ExampleGroups.assign_const(self) hooks.register_globals(self, RSpec.configuration.hooks) RSpec.configuration.configure_group(self) end
@private
# File lib/rspec/core/example_group.rb, line 601 def self.set_ivars(instance, ivars) ivars.each { |name, value| instance.instance_variable_set(name, value) } end
@private
# File lib/rspec/core/example_group.rb, line 461 def self.store_before_context_ivars(example_group_instance) each_instance_variable_for_example(example_group_instance) do |ivar| before_context_ivars[ivar] = example_group_instance.instance_variable_get(ivar) end end
@private
# File lib/rspec/core/example_group.rb, line 360 def self.subclass(parent, description, args, &example_group_block) subclass = Class.new(parent) subclass.set_it_up(description, *args, &example_group_block) subclass.module_exec(&example_group_block) if example_group_block # The LetDefinitions module must be included _after_ other modules # to ensure that it takes precedence when there are name collisions. # Thus, we delay including it until after the example group block # has been eval'd. MemoizedHelpers.define_helpers_on(subclass) subclass end
@private
# File lib/rspec/core/example_group.rb, line 480 def self.superclass_before_context_ivars superclass.before_context_ivars end
@private @return [Metadata] belonging to the parent of a nested {ExampleGroup}
# File lib/rspec/core/example_group.rb, line 67 def self.superclass_metadata @superclass_metadata ||= superclass.respond_to?(:metadata) ? superclass.metadata : nil end
@private
# File lib/rspec/core/example_group.rb, line 440 def self.top_level? superclass == ExampleGroup end
@private
# File lib/rspec/core/example_group.rb, line 596 def self.top_level_description parent_groups.last.description end
Temporarily replace the provided metadata. Intended primarily to allow an example group's singleton class to return the metadata of the example that it exists for. This is necessary for shared example group inclusion to work properly with singleton example groups. @private
# File lib/rspec/core/example_group.rb, line 57 def self.with_replaced_metadata(meta) orig_metadata = metadata @metadata = meta yield ensure @metadata = orig_metadata end
Private Class Methods
# File lib/rspec/core/example_group.rb, line 646 def self.method_missing(name, *args) if method_defined?(name) raise WrongScopeError, "`#{name}` is not available on an example group (e.g. a " "`describe` or `context` block). It is only available from " "within individual examples (e.g. `it` blocks) or from " "constructs that run in the scope of an example (e.g. " "`before`, `let`, etc)." end super end
Public Instance Methods
Returns the class or module passed to the `describe` method (or alias). Returns nil if the subject is not a class or module. @example
describe Thing do it "does something" do described_class == Thing end end
# File lib/rspec/core/example_group.rb, line 95 def described_class self.class.described_class end
@private
# File lib/rspec/core/example_group.rb, line 628 def inspect "#<#{self.class} #{@__inspect_output}>" end
:nocov: @private
# File lib/rspec/core/example_group.rb, line 635 def singleton_class class << self; self; end end
Private Instance Methods
# File lib/rspec/core/example_group.rb, line 662 def method_missing(name, *args) if self.class.respond_to?(name) raise WrongScopeError, "`#{name}` is not available from within an example (e.g. an " "`it` block) or from constructs that run in the scope of an " "example (e.g. `before`, `let`, etc). It is only available " "on an example group (e.g. a `describe` or `context` block)." end super end