Skip to content
This repository
Browse code

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...
commit 43ec3a821fc37b034a0cf0c66d5025eb8805ac23 1 parent 0d26b06
David Chelimsky dchelimsky authored
8 lib/rspec/core/configuration.rb
@@ -538,7 +538,7 @@ def alias_example_to(new_name, *args)
538 538 #
539 539 # Example:
540 540 #
541   - # alias_it_should_behave_like_to(:it_has_behavior, 'has behavior:')
  541 + # alias_it_behaves_like_to(:it_has_behavior, 'has behavior:')
542 542 #
543 543 # allows the user to include a shared example group like:
544 544 #
@@ -553,10 +553,12 @@ def alias_example_to(new_name, *args)
553 553 # Entity
554 554 # has behavior: sortability
555 555 # # sortability examples here
556   - def alias_it_should_behave_like_to(new_name, report_label = '')
557   - RSpec::Core::ExampleGroup.alias_it_should_behave_like_to(new_name, report_label)
  556 + def alias_it_behaves_like_to(new_name, report_label = '')
  557 + RSpec::Core::ExampleGroup.alias_it_behaves_like_to(new_name, report_label)
558 558 end
559 559
  560 + alias_method :alias_it_should_behave_like_to, :alias_it_behaves_like_to
  561 +
560 562 # Adds key/value pairs to the `inclusion_filter`. If the
561 563 # `treat_symbols_as_metadata_keys_with_true_values` config option is set
562 564 # to true and `args` includes any symbols that are not part of a hash,
66 lib/rspec/core/example.rb
@@ -2,10 +2,38 @@ module RSpec
2 2 module Core
3 3 # Wrapper for an instance of a subclass of [ExampleGroup](ExampleGroup). An
4 4 # instance of `Example` is returned by the
5   - # [example](ExampleGroup#example-instance_method) method available in
  5 + # [example](ExampleGroup#example-instance_method) method exposed to
6 6 # examples, [before](Hooks#before-instance_method) and
7 7 # [after](Hooks#after-instance_method) hooks, and yielded to
8 8 # [around](Hooks#around-instance_method) hooks.
  9 + #
  10 + # Useful for configuring logging and/or taking some action based
  11 + # on the state of an example's metadata.
  12 + #
  13 + # @example
  14 + #
  15 + # RSpec.configure do |config|
  16 + # config.before do
  17 + # log example.description
  18 + # end
  19 + #
  20 + # config.after do
  21 + # log example.description
  22 + # end
  23 + #
  24 + # config.around do |ex|
  25 + # log example.description
  26 + # ex.run
  27 + # end
  28 + # end
  29 + #
  30 + # shared_examples "auditable" do
  31 + # it "does something" do
  32 + # log "#{example.full_description}: #{auditable.inspect}"
  33 + # auditable.should do_something
  34 + # end
  35 + # end
  36 + #
9 37 # @see ExampleGroup
10 38 class Example
11 39 # @private
@@ -70,9 +98,9 @@ def example_group
70 98 alias_method :pending?, :pending
71 99
72 100 # @api private
  101 + # instance_evals the block passed to the constructor in the context of
  102 + # the instance of [ExampleGroup](../ExampleGroup).
73 103 # @param example_group_instance the instance of an ExampleGroup subclass
74   - # instance_evals the block submitted to the constructor in the
75   - # context of the instance of ExampleGroup
76 104 def run(example_group_instance, reporter)
77 105 @example_group_instance = example_group_instance
78 106 @example_group_instance.example = self
@@ -112,7 +140,7 @@ def run(example_group_instance, reporter)
112 140 finish(reporter)
113 141 end
114 142
115   - # @private
  143 + # @api private
116 144 #
117 145 # Wraps the example block in a Proc so it can invoked using `run` or
118 146 # `call` in [around](../Hooks#around-instance_method) hooks.
@@ -120,20 +148,40 @@ def self.procsy(metadata, &proc)
120 148 proc.extend(Procsy).with(metadata)
121 149 end
122 150
123   - # @private
  151 + # Used to extend a `Proc` with behavior that makes it look something like
  152 + # an [Example](../Example) in an [around](../Hooks#around-instance_method)
  153 + # hook.
  154 + #
  155 + # @note Procsy, itself, is not a public API, but we're documenting it
  156 + # here to document how to interact with the object yielded to an
  157 + # `around` hook.
  158 + #
  159 + # @example
  160 + #
  161 + # RSpec.configure do |c|
  162 + # c.around do |ex| # ex is a Proc extended with Procsy
  163 + # if ex.metadata[:key] == :some_value && some_global_condition
  164 + # raise "some message"
  165 + # end
  166 + # ex.run # run delegates to ex.call
  167 + # end
  168 + # end
124 169 module Procsy
  170 + # The `metadata` of the [Example](../Example) instance.
125 171 attr_reader :metadata
126 172
127   - # @private
  173 + # @api private
128 174 # @param [Proc]
129 175 # Adds a `run` method to the extended Proc, allowing it to be invoked
130 176 # in an [around](../Hooks#around-instance_method) hook using either
131 177 # `run` or `call`.
132   - def self.extended(object)
133   - def object.run; call; end
  178 + def self.extended(proc)
  179 + # @api public
  180 + # Foo bar
  181 + def proc.run; call; end
134 182 end
135 183
136   - # @private
  184 + # @api private
137 185 def with(metadata)
138 186 @metadata = metadata
139 187 self
150 lib/rspec/core/example_group.rb
... ... @@ -1,7 +1,7 @@
1 1 module RSpec
2 2 module Core
3   - # ExampleGroup and Example are the main structural elements of rspec-core.
4   - # Consider this example:
  3 + # ExampleGroup and [Example](Example) are the main structural elements of
  4 + # rspec-core. Consider this example:
5 5 #
6 6 # describe Thing do
7 7 # it "does something" do
@@ -47,66 +47,108 @@ def self.delegate_to_metadata(*names)
47 47 alias_method :display_name, :description
48 48 # @private
49 49 alias_method :describes, :described_class
50   - end
51   -
52   - # @private
53   - def self.define_example_method(name, extra_options={})
54   - module_eval(<<-END_RUBY, __FILE__, __LINE__)
55   - def self.#{name}(desc=nil, *args, &block)
56   - options = build_metadata_hash_from(args)
57   - options.update(:pending => RSpec::Core::Pending::NOT_YET_IMPLEMENTED) unless block
58   - options.update(#{extra_options.inspect})
59   - examples << RSpec::Core::Example.new(self, desc, options, block)
60   - examples.last
61   - end
62   - END_RUBY
63   - end
64   -
65   - define_example_method :example
66   - define_example_method :it
67   - define_example_method :specify
68 50
69   - define_example_method :focused, :focused => true, :focus => true
70   - define_example_method :focus, :focused => true, :focus => true
71   -
72   - define_example_method :pending, :pending => true
73   - define_example_method :xexample, :pending => 'Temporarily disabled with xexample'
74   - define_example_method :xit, :pending => 'Temporarily disabled with xit'
75   - define_example_method :xspecify, :pending => 'Temporarily disabled with xspecify'
  51 + # @private
  52 + # @macro [attach] define_example_method
  53 + # @param [String] name
  54 + # @param [Hash] extra_options
  55 + # @param [Block] implementation
  56 + def self.define_example_method(name, extra_options={})
  57 + module_eval(<<-END_RUBY, __FILE__, __LINE__)
  58 + def #{name}(desc=nil, *args, &block)
  59 + options = build_metadata_hash_from(args)
  60 + options.update(:pending => RSpec::Core::Pending::NOT_YET_IMPLEMENTED) unless block
  61 + options.update(#{extra_options.inspect})
  62 + examples << RSpec::Core::Example.new(self, desc, options, block)
  63 + examples.last
  64 + end
  65 + END_RUBY
  66 + end
76 67
77   - class << self
78   - alias_method :alias_example_to, :define_example_method
79   - end
  68 + # Defines an example within a group.
  69 + define_example_method :example
  70 + # Defines an example within a group.
  71 + #
  72 + # @see ExampleGroup::example
  73 + define_example_method :it
  74 + # Defines an example within a group.
  75 + # This is here primarily for backward compatibility with early versions
  76 + # of RSpec which used `context` and `specify` instead of `describe` and
  77 + # `it`.
  78 + define_example_method :specify
  79 +
  80 + # Shortcut to define an example with `:focus` => true
  81 + define_example_method :focus, :focused => true, :focus => true
  82 + # Shortcut to define an example with `:focus` => true
  83 + define_example_method :focused, :focused => true, :focus => true
  84 +
  85 + # Shortcut to define an example with :pending => true
  86 + define_example_method :pending, :pending => true
  87 + # Shortcut to define an example with :pending => 'Temporarily disabled with xexample'
  88 + define_example_method :xexample, :pending => 'Temporarily disabled with xexample'
  89 + # Shortcut to define an example with :pending => 'Temporarily disabled with xit'
  90 + define_example_method :xit, :pending => 'Temporarily disabled with xit'
  91 + # Shortcut to define an example with :pending => 'Temporarily disabled with xspecify'
  92 + define_example_method :xspecify, :pending => 'Temporarily disabled with xspecify'
  93 +
  94 + # Works like `alias_method :name, :example` with the added benefit of
  95 + # assigning default metadata to the generated example.
  96 + #
  97 + # @note Use with caution. This extends the language used in your
  98 + # specs, but does not add any additional documentation. We use this
  99 + # in rspec to define methods like `focus` and `xit`, but we also add
  100 + # docs for those methods.
  101 + def alias_example_to name, extra={}
  102 + (class << self; self; end).define_example_method name, extra
  103 + end
80 104
81   - # @private
82   - def self.define_nested_shared_group_method(new_name, report_label=nil)
83   - module_eval(<<-END_RUBY, __FILE__, __LINE__)
84   - def self.#{new_name}(name, *args, &customization_block)
85   - group = describe("#{report_label || "it should behave like"} \#{name}") do
86   - find_and_eval_shared("examples", name, *args, &customization_block)
  105 + # @private
  106 + # @macro [attach] define_nested_shared_group_method
  107 + #
  108 + # @see SharedExampleGroup
  109 + def self.define_nested_shared_group_method(new_name, report_label=nil)
  110 + module_eval(<<-END_RUBY, __FILE__, __LINE__)
  111 + def #{new_name}(name, *args, &customization_block)
  112 + group = describe("#{report_label || "it should behave like"} \#{name}") do
  113 + find_and_eval_shared("examples", name, *args, &customization_block)
  114 + end
  115 + group.metadata[:shared_group_name] = name
  116 + group
87 117 end
88   - group.metadata[:shared_group_name] = name
89   - group
90   - end
91   - END_RUBY
92   - end
93   -
94   - define_nested_shared_group_method :it_should_behave_like
  118 + END_RUBY
  119 + end
95 120
96   - class << self
97   - alias_method :alias_it_should_behave_like_to, :define_nested_shared_group_method
  121 + # Generates a nested example group and includes the shared content
  122 + # mapped to `name` in the nested group.
  123 + define_nested_shared_group_method :it_behaves_like, "behaves like"
  124 + # Generates a nested example group and includes the shared content
  125 + # mapped to `name` in the nested group.
  126 + define_nested_shared_group_method :it_should_behave_like
  127 +
  128 + # Works like `alias_method :name, :it_behaves_like` with the added
  129 + # benefit of assigning default metadata to the generated example.
  130 + #
  131 + # @note Use with caution. This extends the language used in your
  132 + # specs, but does not add any additional documentation. We use this
  133 + # in rspec to define `it_should_behave_like` (for backward
  134 + # compatibility), but we also add docs for that method.
  135 + def alias_it_behaves_like_to name, *args, &block
  136 + (class << self; self; end).define_nested_shared_group_method name, *args, &block
  137 + end
98 138 end
99 139
100   - alias_it_should_behave_like_to :it_behaves_like, "behaves like"
101   -
102   - # Includes shared content declared with `name`.
  140 + # Includes shared content mapped to `name` directly in the group in which
  141 + # it is declared. Unlike `it_behaves_like`, this does not create a nested
  142 + # example group, nor does it accept a block.
103 143 #
104 144 # @see SharedExampleGroup
105 145 def self.include_context(name, *args)
106 146 block_given? ? block_not_supported("context") : find_and_eval_shared("context", name, *args)
107 147 end
108 148
109   - # Includes shared content declared with `name`.
  149 + # Includes shared content mapped to `name` directly in the group in which
  150 + # it is declared. Unlike `it_behaves_like`, this does not create a nested
  151 + # example group, nor does it accept a block.
110 152 #
111 153 # @see SharedExampleGroup
112 154 def self.include_examples(name, *args)
@@ -121,7 +163,7 @@ def self.block_not_supported(label)
121 163 # @private
122 164 def self.find_and_eval_shared(label, name, *args, &customization_block)
123 165 raise ArgumentError, "Could not find shared #{label} #{name.inspect}" unless
124   - shared_block = world.shared_example_groups[name]
  166 + shared_block = world.shared_example_groups[name]
125 167
126 168 module_eval_with_args(*args, &shared_block)
127 169 module_eval(&customization_block) if customization_block
@@ -314,11 +356,11 @@ def self.run_after_all_hooks(example_group_instance)
314 356 # TODO: come up with a better solution for this.
315 357 RSpec.configuration.reporter.message <<-EOS
316 358
317   -An error occurred in an after(:all) hook.
318   - #{e.class}: #{e.message}
319   - occurred at #{e.backtrace.first}
  359 + An error occurred in an after(:all) hook.
  360 + #{e.class}: #{e.message}
  361 + occurred at #{e.backtrace.first}
320 362
321   - EOS
  363 + EOS
322 364 end
323 365 end
324 366
52 lib/rspec/core/shared_example_group.rb
... ... @@ -1,27 +1,46 @@
1 1 module RSpec
2 2 module Core
3 3 module SharedExampleGroup
4   -
5 4 # @overload shared_examples(name, &block)
6 5 # @overload shared_examples(name, tags, &block)
7 6 #
8   - # Creates and stores (but does not evaluate) the block.
  7 + # Wraps the `block` in a module which can then be included in example
  8 + # groups using `include_examples`, `include_context`, or
  9 + # `it_behaves_like`.
  10 + #
  11 + # @param [String] name to match when looking up this shared group
  12 + # @param block to be eval'd in a nested example group generated by `it_behaves_like`
  13 + #
  14 + # @example
  15 + #
  16 + # shared_examples "auditable" do
  17 + # it "stores an audit record on save!" do
  18 + # lambda { auditable.save! }.should change(Audit, :count).by(1)
  19 + # end
  20 + # end
  21 + #
  22 + # class Account do
  23 + # it_behaves_like "auditable" do
  24 + # def auditable; Account.new; end
  25 + # end
  26 + # end
9 27 #
  28 + # @see ExampleGroup.it_behaves_like
10 29 # @see ExampleGroup.include_examples
11 30 # @see ExampleGroup.include_context
12   - def shared_examples(*args, &block)
13   - if [String, Symbol, Module].any? {|cls| cls === args.first }
14   - object = args.shift
15   - ensure_shared_example_group_name_not_taken(object)
16   - RSpec.world.shared_example_groups[object] = block
  31 + def shared_examples *args, &block
  32 + if key? args.first
  33 + key = args.shift
  34 + raise_key_taken key if key_taken? key
  35 + RSpec.world.shared_example_groups[key] = block
17 36 end
18 37
19 38 unless args.empty?
20 39 mod = Module.new
21   - (class << mod; self; end).send(:define_method, :extended) do |host|
22   - host.class_eval(&block)
  40 + (class << mod; self; end).send :define_method, :extended do |host|
  41 + host.class_eval &block
23 42 end
24   - RSpec.configuration.extend(mod, *args)
  43 + RSpec.configuration.extend mod, *args
25 44 end
26 45 end
27 46
@@ -55,16 +74,21 @@ def self.included(kls)
55 74
56 75 private
57 76
  77 + def key? candidate
  78 + [String, Symbol, Module].any? { |cls| cls === candidate }
  79 + end
  80 +
58 81 def raise_name_error
59 82 raise NameError, "The first argument (#{name}) to share_as must be a legal name for a constant not already in use."
60 83 end
61 84
62   - def ensure_shared_example_group_name_not_taken(name)
63   - if RSpec.world.shared_example_groups.has_key?(name)
64   - raise ArgumentError.new("Shared example group '#{name}' already exists")
65   - end
  85 + def raise_key_taken key
  86 + raise ArgumentError, "Shared example group '#{key}' already exists"
66 87 end
67 88
  89 + def key_taken? key
  90 + RSpec.world.shared_example_groups.has_key?(key)
  91 + end
68 92 end
69 93 end
70 94 end
2  spec/spec_helper.rb
@@ -68,7 +68,7 @@ def in_editor?
68 68
69 69 RSpec.configure do |c|
70 70 # structural
71   - c.alias_it_should_behave_like_to 'it_has_behavior'
  71 + c.alias_it_behaves_like_to 'it_has_behavior'
72 72 c.around {|example| sandboxed { example.run }}
73 73 c.include(RSpecHelpers)
74 74 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.