class RSpec::Core::ExampleGroup
ExampleGroup
and {Example} are the main structural elements of rspec-core. Consider this example:
RSpec.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
Adds an example to the example group
# File rspec-core/lib/rspec/core/example_group.rb, line 374 def self.add_example(example) reset_memoized examples << example end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 536 def self.before_context_ivars @before_context_ivars ||= {} end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 473 def self.children @children ||= [] end
Returns true if a ‘before(:context)` or `after(:context)` hook is currently executing.
# File rspec-core/lib/rspec/core/example_group.rb, line 549 def self.currently_executing_a_context_hook? @currently_executing_a_context_hook end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 674 def self.declaration_locations @declaration_locations ||= [Metadata.location_tuple_from(metadata)] + examples.map { |e| Metadata.location_tuple_from(e.metadata) } + FlatMap.flat_map(children, &:declaration_locations) end
@private @macro [attach] define_example_group_method
@!scope class @method $1 @overload $1 @overload $1(&example_group_definition) @param example_group_definition [Block] The definition of the example group. @overload $1(doc_string, *metadata, &example_implementation) @param doc_string [String] The group's doc string. @param metadata [Array<Symbol>, Hash] Metadata for the group. Symbols will be transformed into hash entries with `true` values. @param example_group_definition [Block] The definition of the example group. @return [RSpec::Core::ExampleGroup] 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 before(:example, :clean_env) do env.clear! 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 $1 "needs additional setup", :clean_env, :implementation => JSON do # specifies that hooks with matching metadata # should be be run additionally end end
@see DSL#describe
# File rspec-core/lib/rspec/core/example_group.rb, line 248 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 registration_collection = 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 RSpec.world.example_groups else children 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, registration_collection, &example_group_block) 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 @method $1 @overload $1 @overload $1(&example_implementation) @param example_implementation [Block] The implementation of the example. @overload $1(doc_string, *metadata) @param doc_string [String] The example's doc string. @param metadata [Array<Symbol>, Hash] Metadata for the example. Symbols will be transformed into hash entries with `true` values. @overload $1(doc_string, *metadata, &example_implementation) @param doc_string [String] The example's doc string. @param metadata [Array<Symbol>, Hash] Metadata for the example. Symbols 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 @example $1 "does something", :slow, :load_factor => 100 do end
# File rspec-core/lib/rspec/core/example_group.rb, line 145 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) RSpec::Core::Example.new(self, desc, options, block) end end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 76 def self.delegate_to_metadata(*names) names.each do |name| idempotently_define_singleton_method(name) { metadata.fetch(name) } end end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 467 def self.descendant_filtered_examples @descendant_filtered_examples ||= filtered_examples + FlatMap.flat_map(children, &:descendant_filtered_examples) end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 510 def self.descendants @_descendants ||= [self] + FlatMap.flat_map(children, &:descendants) end
@return [String] the current example group description
# File rspec-core/lib/rspec/core/example_group.rb, line 85 def self.description description = metadata[:description] RSpec.configuration.format_docstrings_block.call(description) end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 707 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 rspec-core/lib/rspec/core/example_group.rb, line 525 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 rspec-core/lib/rspec/core/example_group.rb, line 457 def self.examples @examples ||= [] end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 462 def self.filtered_examples RSpec.world.filtered_examples[self] end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 662 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 rspec-core/lib/rspec/core/example_group.rb, line 682 def self.id Metadata.id_from(metadata) end
Define a singleton method for the singleton class (remove the method if it’s already been defined). @private
# File rspec-core/lib/rspec/core/example_group.rb, line 40 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 rspec-core/lib/rspec/core/example_group.rb, line 350 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 rspec-core/lib/rspec/core/example_group.rb, line 360 def self.include_examples(name, *args, &block) find_and_eval_shared("examples", name, caller.first, *args, &block) end
@private
RSpec::Core::MemoizedHelpers::new
# File rspec-core/lib/rspec/core/example_group.rb, line 714 def initialize(inspect_output=nil) @__inspect_output = inspect_output || '(no description provided)' super() # no args get passed end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 492 def self.next_runnable_index_for(file) if self == ExampleGroup # We add 1 so the ids start at 1 instead of 0. This is # necessary for this branch (but not for the other one) # because we register examples and groups with the # `children` and `examples` collection BEFORE this # method is called as part of metadata hash creation, # but the example group is recorded with # `RSpec.world.example_group_counts_by_spec_file` AFTER # the metadata hash is created and the group is returned # to the caller. RSpec.world.num_example_groups_defined_in(file) + 1 else children.count + examples.count end end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 632 def self.ordering_strategy order = metadata.fetch(:order, :global) registry = RSpec.configuration.ordering_registry registry.fetch(order) do warn <<-WARNING.gsub(/^ +\|/, '') |WARNING: Ignoring unknown ordering specified using `:order => #{order.inspect}` metadata. | Falling back to configured global ordering. | Unrecognized ordering specified at: #{location} WARNING registry.fetch(:global) end end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 515 def self.parent_groups @parent_groups ||= ancestors.select { |a| a < RSpec::Core::ExampleGroup } end
Removes an example from the example group
# File rspec-core/lib/rspec/core/example_group.rb, line 380 def self.remove_example(example) reset_memoized examples.delete example end
Clear memoized values when adding/removing examples @private
# File rspec-core/lib/rspec/core/example_group.rb, line 366 def self.reset_memoized @descendant_filtered_examples = nil @_descendants = nil @parent_groups = nil @declaration_locations = nil end
Runs all the examples in this group.
# File rspec-core/lib/rspec/core/example_group.rb, line 606 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 RSpec.current_scope = :before_context_hook 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 Support::AllExceptionsExceptOnesWeMustNotRescue => ex for_filtered_examples(reporter) { |example| example.fail_with_exception(reporter, ex) } RSpec.world.wants_to_quit = true if reporter.fail_fast_limit_met? false ensure RSpec.current_scope = :after_context_hook run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks reporter.example_group_finished(self) end end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 592 def self.run_after_context_hooks(example_group_instance) set_ivars(example_group_instance, before_context_ivars) @currently_executing_a_context_hook = true ContextHookMemoized::After.isolate_for_context_hook(example_group_instance) do hooks.run(:after, :context, example_group_instance) end ensure before_context_ivars.clear @currently_executing_a_context_hook = false end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 554 def self.run_before_context_hooks(example_group_instance) set_ivars(example_group_instance, superclass_before_context_ivars) @currently_executing_a_context_hook = true 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) @currently_executing_a_context_hook = false end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 648 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) if !succeeded && reporter.fail_fast_limit_met? RSpec.world.wants_to_quit = true end succeeded end.all? end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 417 def self.set_it_up(description, args, registration_collection, &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 # Register the example with the group before creating the metadata hash. # This is necessary since creating the metadata hash triggers # `when_first_matching_example_defined` callbacks, in which users can # load RSpec support code which defines hooks. For that to work, the # examples and example groups must be registered at the time the # support code is called or be defined afterwards. # Begin defined beforehand but registered afterwards causes hooks to # not be applied where they should. registration_collection << self @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 ) config = RSpec.configuration config.apply_derived_metadata_to(@metadata) ExampleGroups.assign_const(self) @currently_executing_a_context_hook = false config.configure_group(self) end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 692 def self.set_ivars(instance, ivars) ivars.each { |name, value| instance.instance_variable_set(name, value) } end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 541 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 rspec-core/lib/rspec/core/example_group.rb, line 402 def self.subclass(parent, description, args, registration_collection, &example_group_block) subclass = Class.new(parent) subclass.set_it_up(description, args, registration_collection, &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 rspec-core/lib/rspec/core/example_group.rb, line 569 def self.superclass_before_context_ivars superclass.before_context_ivars end
@private @return [Metadata] belonging to the parent of a nested {ExampleGroup}
# File rspec-core/lib/rspec/core/example_group.rb, line 71 def self.superclass_metadata @superclass_metadata ||= superclass.respond_to?(:metadata) ? superclass.metadata : nil end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 520 def self.top_level? superclass == ExampleGroup end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 687 def self.top_level_description parent_groups.last.description end
@private Traverses the tree of groups, starting with ‘self`, then the children, recursively. Halts the traversal of a branch of the tree as soon as the passed block returns true. Note that siblings groups and their sub-trees will continue to be explored. This is intended to make it easy to find the top-most group that satisfies some condition.
# File rspec-core/lib/rspec/core/example_group.rb, line 483 def self.traverse_tree_until(&block) return if yield self children.each do |child| child.traverse_tree_until(&block) end end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 734 def self.update_inherited_metadata(updates) metadata.update(updates) do |key, existing_group_value, new_inherited_value| @user_metadata.key?(key) ? existing_group_value : new_inherited_value end RSpec.configuration.configure_group(self) examples.each { |ex| ex.update_inherited_metadata(updates) } children.each { |group| group.update_inherited_metadata(updates) } 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 rspec-core/lib/rspec/core/example_group.rb, line 61 def self.with_replaced_metadata(meta) orig_metadata = metadata @metadata = meta yield ensure @metadata = orig_metadata 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
RSpec.describe Thing do it "does something" do described_class == Thing end end
# File rspec-core/lib/rspec/core/example_group.rb, line 99 def described_class self.class.described_class end
@private
# File rspec-core/lib/rspec/core/example_group.rb, line 720 def inspect "#<#{self.class} #{@__inspect_output}>" end
:nocov: @private
# File rspec-core/lib/rspec/core/example_group.rb, line 727 def singleton_class class << self; self; end end