rspec-mocks 2.11.3 → 3.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +989 -21
  6. data/{License.txt → LICENSE.md} +5 -3
  7. data/README.md +260 -73
  8. data/lib/rspec/mocks/any_instance/chain.rb +58 -24
  9. data/lib/rspec/mocks/any_instance/error_generator.rb +31 -0
  10. data/lib/rspec/mocks/any_instance/expect_chain_chain.rb +31 -0
  11. data/lib/rspec/mocks/any_instance/expectation_chain.rb +23 -30
  12. data/lib/rspec/mocks/any_instance/message_chains.rb +38 -15
  13. data/lib/rspec/mocks/any_instance/proxy.rb +116 -0
  14. data/lib/rspec/mocks/any_instance/recorder.rb +155 -59
  15. data/lib/rspec/mocks/any_instance/stub_chain.rb +33 -19
  16. data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +10 -12
  17. data/lib/rspec/mocks/any_instance.rb +11 -81
  18. data/lib/rspec/mocks/argument_list_matcher.rb +59 -37
  19. data/lib/rspec/mocks/argument_matchers.rb +233 -149
  20. data/lib/rspec/mocks/configuration.rb +212 -0
  21. data/lib/rspec/mocks/error_generator.rb +304 -49
  22. data/lib/rspec/mocks/example_methods.rb +361 -22
  23. data/lib/rspec/mocks/instance_method_stasher.rb +146 -0
  24. data/lib/rspec/mocks/marshal_extension.rb +41 -0
  25. data/lib/rspec/mocks/matchers/expectation_customization.rb +20 -0
  26. data/lib/rspec/mocks/matchers/have_received.rb +134 -0
  27. data/lib/rspec/mocks/matchers/receive.rb +133 -0
  28. data/lib/rspec/mocks/matchers/receive_message_chain.rb +82 -0
  29. data/lib/rspec/mocks/matchers/receive_messages.rb +77 -0
  30. data/lib/rspec/mocks/message_chain.rb +87 -0
  31. data/lib/rspec/mocks/message_expectation.rb +648 -314
  32. data/lib/rspec/mocks/method_double.rb +185 -58
  33. data/lib/rspec/mocks/method_reference.rb +202 -0
  34. data/lib/rspec/mocks/minitest_integration.rb +68 -0
  35. data/lib/rspec/mocks/mutate_const.rb +339 -0
  36. data/lib/rspec/mocks/object_reference.rb +149 -0
  37. data/lib/rspec/mocks/order_group.rb +48 -7
  38. data/lib/rspec/mocks/proxy.rb +405 -74
  39. data/lib/rspec/mocks/space.rb +219 -15
  40. data/lib/rspec/mocks/standalone.rb +2 -2
  41. data/lib/rspec/mocks/syntax.rb +325 -0
  42. data/lib/rspec/mocks/targets.rb +124 -0
  43. data/lib/rspec/mocks/test_double.rb +125 -57
  44. data/lib/rspec/mocks/verifying_double.rb +121 -0
  45. data/lib/rspec/mocks/verifying_message_expectation.rb +54 -0
  46. data/lib/rspec/mocks/verifying_proxy.rb +220 -0
  47. data/lib/rspec/mocks/version.rb +3 -1
  48. data/lib/rspec/mocks.rb +121 -27
  49. data.tar.gz.sig +0 -0
  50. metadata +178 -253
  51. metadata.gz.sig +3 -0
  52. data/features/README.md +0 -67
  53. data/features/Scope.md +0 -17
  54. data/features/Upgrade.md +0 -22
  55. data/features/argument_matchers/README.md +0 -27
  56. data/features/argument_matchers/explicit.feature +0 -60
  57. data/features/argument_matchers/general_matchers.feature +0 -85
  58. data/features/argument_matchers/type_matchers.feature +0 -27
  59. data/features/message_expectations/README.md +0 -69
  60. data/features/message_expectations/any_instance.feature +0 -21
  61. data/features/message_expectations/block_local_expectations.feature.pending +0 -55
  62. data/features/message_expectations/expect_message.feature +0 -94
  63. data/features/message_expectations/receive_counts.feature +0 -209
  64. data/features/message_expectations/warn_when_expectation_is_set_on_nil.feature +0 -50
  65. data/features/method_stubs/README.md +0 -47
  66. data/features/method_stubs/any_instance.feature +0 -133
  67. data/features/method_stubs/as_null_object.feature +0 -35
  68. data/features/method_stubs/simple_return_value.feature +0 -64
  69. data/features/method_stubs/stub_chain.feature +0 -51
  70. data/features/method_stubs/stub_implementation.feature +0 -26
  71. data/features/method_stubs/to_ary.feature +0 -47
  72. data/features/outside_rspec/configuration.feature +0 -82
  73. data/features/outside_rspec/standalone.feature +0 -32
  74. data/features/step_definitions/additional_cli_steps.rb +0 -4
  75. data/features/stubbing_constants/README.md +0 -62
  76. data/features/stubbing_constants/stub_defined_constant.feature +0 -79
  77. data/features/stubbing_constants/stub_undefined_constant.feature +0 -50
  78. data/features/support/env.rb +0 -6
  79. data/lib/rspec/mocks/errors.rb +0 -12
  80. data/lib/rspec/mocks/extensions/instance_exec.rb +0 -34
  81. data/lib/rspec/mocks/extensions/marshal.rb +0 -23
  82. data/lib/rspec/mocks/extensions/psych.rb +0 -23
  83. data/lib/rspec/mocks/framework.rb +0 -21
  84. data/lib/rspec/mocks/methods.rb +0 -155
  85. data/lib/rspec/mocks/mock.rb +0 -7
  86. data/lib/rspec/mocks/serialization.rb +0 -34
  87. data/lib/rspec/mocks/stashed_instance_method.rb +0 -60
  88. data/lib/rspec/mocks/stub_const.rb +0 -332
  89. data/lib/spec/mocks.rb +0 -2
  90. data/spec/rspec/mocks/and_yield_spec.rb +0 -114
  91. data/spec/rspec/mocks/any_instance/message_chains_spec.rb +0 -40
  92. data/spec/rspec/mocks/any_instance_spec.rb +0 -877
  93. data/spec/rspec/mocks/any_number_of_times_spec.rb +0 -30
  94. data/spec/rspec/mocks/argument_expectation_spec.rb +0 -34
  95. data/spec/rspec/mocks/at_least_spec.rb +0 -142
  96. data/spec/rspec/mocks/at_most_spec.rb +0 -90
  97. data/spec/rspec/mocks/block_return_value_spec.rb +0 -53
  98. data/spec/rspec/mocks/bug_report_10260_spec.rb +0 -8
  99. data/spec/rspec/mocks/bug_report_10263_spec.rb +0 -25
  100. data/spec/rspec/mocks/bug_report_11545_spec.rb +0 -32
  101. data/spec/rspec/mocks/bug_report_496_spec.rb +0 -17
  102. data/spec/rspec/mocks/bug_report_600_spec.rb +0 -22
  103. data/spec/rspec/mocks/bug_report_7611_spec.rb +0 -16
  104. data/spec/rspec/mocks/bug_report_8165_spec.rb +0 -31
  105. data/spec/rspec/mocks/bug_report_830_spec.rb +0 -21
  106. data/spec/rspec/mocks/bug_report_957_spec.rb +0 -22
  107. data/spec/rspec/mocks/double_spec.rb +0 -12
  108. data/spec/rspec/mocks/failing_argument_matchers_spec.rb +0 -95
  109. data/spec/rspec/mocks/hash_excluding_matcher_spec.rb +0 -67
  110. data/spec/rspec/mocks/hash_including_matcher_spec.rb +0 -90
  111. data/spec/rspec/mocks/mock_ordering_spec.rb +0 -103
  112. data/spec/rspec/mocks/mock_space_spec.rb +0 -58
  113. data/spec/rspec/mocks/mock_spec.rb +0 -730
  114. data/spec/rspec/mocks/multiple_return_value_spec.rb +0 -119
  115. data/spec/rspec/mocks/nil_expectation_warning_spec.rb +0 -62
  116. data/spec/rspec/mocks/null_object_mock_spec.rb +0 -106
  117. data/spec/rspec/mocks/once_counts_spec.rb +0 -52
  118. data/spec/rspec/mocks/options_hash_spec.rb +0 -35
  119. data/spec/rspec/mocks/partial_mock_spec.rb +0 -171
  120. data/spec/rspec/mocks/partial_mock_using_mocks_directly_spec.rb +0 -95
  121. data/spec/rspec/mocks/passing_argument_matchers_spec.rb +0 -142
  122. data/spec/rspec/mocks/precise_counts_spec.rb +0 -68
  123. data/spec/rspec/mocks/record_messages_spec.rb +0 -26
  124. data/spec/rspec/mocks/serialization_spec.rb +0 -111
  125. data/spec/rspec/mocks/stash_spec.rb +0 -27
  126. data/spec/rspec/mocks/stashed_instance_method_spec.rb +0 -53
  127. data/spec/rspec/mocks/stub_chain_spec.rb +0 -154
  128. data/spec/rspec/mocks/stub_const_spec.rb +0 -334
  129. data/spec/rspec/mocks/stub_implementation_spec.rb +0 -81
  130. data/spec/rspec/mocks/stub_spec.rb +0 -247
  131. data/spec/rspec/mocks/stubbed_message_expectations_spec.rb +0 -47
  132. data/spec/rspec/mocks/test_double_spec.rb +0 -57
  133. data/spec/rspec/mocks/to_ary_spec.rb +0 -40
  134. data/spec/rspec/mocks/twice_counts_spec.rb +0 -66
  135. data/spec/rspec/mocks_spec.rb +0 -51
  136. data/spec/spec_helper.rb +0 -21
@@ -0,0 +1,339 @@
1
+ RSpec::Support.require_rspec_support 'recursive_const_methods'
2
+
3
+ module RSpec
4
+ module Mocks
5
+ # Provides information about constants that may (or may not)
6
+ # have been mutated by rspec-mocks.
7
+ class Constant
8
+ extend Support::RecursiveConstMethods
9
+
10
+ # @api private
11
+ def initialize(name)
12
+ @name = name
13
+ @previously_defined = false
14
+ @stubbed = false
15
+ @hidden = false
16
+ @valid_name = true
17
+ yield self if block_given?
18
+ end
19
+
20
+ # @return [String] The fully qualified name of the constant.
21
+ attr_reader :name
22
+
23
+ # @return [Object, nil] The original value (e.g. before it
24
+ # was mutated by rspec-mocks) of the constant, or
25
+ # nil if the constant was not previously defined.
26
+ attr_accessor :original_value
27
+
28
+ # @private
29
+ attr_writer :previously_defined, :stubbed, :hidden, :valid_name
30
+
31
+ # @return [Boolean] Whether or not the constant was defined
32
+ # before the current example.
33
+ def previously_defined?
34
+ @previously_defined
35
+ end
36
+
37
+ # @return [Boolean] Whether or not rspec-mocks has mutated
38
+ # (stubbed or hidden) this constant.
39
+ def mutated?
40
+ @stubbed || @hidden
41
+ end
42
+
43
+ # @return [Boolean] Whether or not rspec-mocks has stubbed
44
+ # this constant.
45
+ def stubbed?
46
+ @stubbed
47
+ end
48
+
49
+ # @return [Boolean] Whether or not rspec-mocks has hidden
50
+ # this constant.
51
+ def hidden?
52
+ @hidden
53
+ end
54
+
55
+ # @return [Boolean] Whether or not the provided constant name
56
+ # is a valid Ruby constant name.
57
+ def valid_name?
58
+ @valid_name
59
+ end
60
+
61
+ # The default `to_s` isn't very useful, so a custom version is provided.
62
+ def to_s
63
+ "#<#{self.class.name} #{name}>"
64
+ end
65
+ alias inspect to_s
66
+
67
+ # @private
68
+ def self.unmutated(name)
69
+ previously_defined = !!recursive_const_defined?(name)
70
+ rescue NameError
71
+ new(name) do |c|
72
+ c.valid_name = false
73
+ end
74
+ else
75
+ new(name) do |const|
76
+ const.previously_defined = previously_defined
77
+ const.original_value = recursive_const_get(name) if previously_defined
78
+ end
79
+ end
80
+
81
+ # Queries rspec-mocks to find out information about the named constant.
82
+ #
83
+ # @param [String] name the name of the constant
84
+ # @return [Constant] an object contaning information about the named
85
+ # constant.
86
+ def self.original(name)
87
+ mutator = ::RSpec::Mocks.space.constant_mutator_for(name)
88
+ mutator ? mutator.to_constant : unmutated(name)
89
+ end
90
+ end
91
+
92
+ # Provides a means to stub constants.
93
+ class ConstantMutator
94
+ extend Support::RecursiveConstMethods
95
+
96
+ # Stubs a constant.
97
+ #
98
+ # @param (see ExampleMethods#stub_const)
99
+ # @option (see ExampleMethods#stub_const)
100
+ # @return (see ExampleMethods#stub_const)
101
+ #
102
+ # @see ExampleMethods#stub_const
103
+ # @note It's recommended that you use `stub_const` in your
104
+ # examples. This is an alternate public API that is provided
105
+ # so you can stub constants in other contexts (e.g. helper
106
+ # classes).
107
+ def self.stub(constant_name, value, options={})
108
+ unless String === constant_name
109
+ raise ArgumentError, "`stub_const` requires a String, but you provided a #{constant_name.class.name}"
110
+ end
111
+
112
+ mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
113
+ DefinedConstantReplacer
114
+ else
115
+ UndefinedConstantSetter
116
+ end
117
+
118
+ mutate(mutator.new(constant_name, value, options[:transfer_nested_constants]))
119
+ value
120
+ end
121
+
122
+ # Hides a constant.
123
+ #
124
+ # @param (see ExampleMethods#hide_const)
125
+ #
126
+ # @see ExampleMethods#hide_const
127
+ # @note It's recommended that you use `hide_const` in your
128
+ # examples. This is an alternate public API that is provided
129
+ # so you can hide constants in other contexts (e.g. helper
130
+ # classes).
131
+ def self.hide(constant_name)
132
+ mutate(ConstantHider.new(constant_name, nil, {}))
133
+ nil
134
+ end
135
+
136
+ # Contains common functionality used by all of the constant mutators.
137
+ #
138
+ # @private
139
+ class BaseMutator
140
+ include Support::RecursiveConstMethods
141
+
142
+ attr_reader :original_value, :full_constant_name
143
+
144
+ def initialize(full_constant_name, mutated_value, transfer_nested_constants)
145
+ @full_constant_name = normalize_const_name(full_constant_name)
146
+ @mutated_value = mutated_value
147
+ @transfer_nested_constants = transfer_nested_constants
148
+ @context_parts = @full_constant_name.split('::')
149
+ @const_name = @context_parts.pop
150
+ @reset_performed = false
151
+ end
152
+
153
+ def to_constant
154
+ const = Constant.new(full_constant_name)
155
+ const.original_value = original_value
156
+
157
+ const
158
+ end
159
+
160
+ def idempotently_reset
161
+ reset unless @reset_performed
162
+ @reset_performed = true
163
+ end
164
+ end
165
+
166
+ # Hides a defined constant for the duration of an example.
167
+ #
168
+ # @private
169
+ class ConstantHider < BaseMutator
170
+ def mutate
171
+ return unless (@defined = recursive_const_defined?(full_constant_name))
172
+ @context = recursive_const_get(@context_parts.join('::'))
173
+ @original_value = get_const_defined_on(@context, @const_name)
174
+
175
+ @context.__send__(:remove_const, @const_name)
176
+ end
177
+
178
+ def to_constant
179
+ return Constant.unmutated(full_constant_name) unless @defined
180
+
181
+ const = super
182
+ const.hidden = true
183
+ const.previously_defined = true
184
+
185
+ const
186
+ end
187
+
188
+ def reset
189
+ return unless @defined
190
+ @context.const_set(@const_name, @original_value)
191
+ end
192
+ end
193
+
194
+ # Replaces a defined constant for the duration of an example.
195
+ #
196
+ # @private
197
+ class DefinedConstantReplacer < BaseMutator
198
+ def initialize(*args)
199
+ super
200
+ @constants_to_transfer = []
201
+ end
202
+
203
+ def mutate
204
+ @context = recursive_const_get(@context_parts.join('::'))
205
+ @original_value = get_const_defined_on(@context, @const_name)
206
+
207
+ @constants_to_transfer = verify_constants_to_transfer!
208
+
209
+ @context.__send__(:remove_const, @const_name)
210
+ @context.const_set(@const_name, @mutated_value)
211
+
212
+ transfer_nested_constants
213
+ end
214
+
215
+ def to_constant
216
+ const = super
217
+ const.stubbed = true
218
+ const.previously_defined = true
219
+
220
+ const
221
+ end
222
+
223
+ def reset
224
+ @constants_to_transfer.each do |const|
225
+ @mutated_value.__send__(:remove_const, const)
226
+ end
227
+
228
+ @context.__send__(:remove_const, @const_name)
229
+ @context.const_set(@const_name, @original_value)
230
+ end
231
+
232
+ def transfer_nested_constants
233
+ @constants_to_transfer.each do |const|
234
+ @mutated_value.const_set(const, get_const_defined_on(original_value, const))
235
+ end
236
+ end
237
+
238
+ def verify_constants_to_transfer!
239
+ return [] unless should_transfer_nested_constants?
240
+
241
+ { @original_value => "the original value", @mutated_value => "the stubbed value" }.each do |value, description|
242
+ next if value.respond_to?(:constants)
243
+
244
+ raise ArgumentError,
245
+ "Cannot transfer nested constants for #{@full_constant_name} " \
246
+ "since #{description} is not a class or module and only classes " \
247
+ "and modules support nested constants."
248
+ end
249
+
250
+ if Array === @transfer_nested_constants
251
+ @transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
252
+ undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value)
253
+
254
+ if undefined_constants.any?
255
+ available_constants = constants_defined_on(@original_value) - @transfer_nested_constants
256
+ raise ArgumentError,
257
+ "Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " \
258
+ "for #{@full_constant_name} since they are not defined. Did you mean " \
259
+ "#{available_constants.join(' or ')}?"
260
+ end
261
+
262
+ @transfer_nested_constants
263
+ else
264
+ constants_defined_on(@original_value)
265
+ end
266
+ end
267
+
268
+ def should_transfer_nested_constants?
269
+ return true if @transfer_nested_constants
270
+ return false unless RSpec::Mocks.configuration.transfer_nested_constants?
271
+ @original_value.respond_to?(:constants) && @mutated_value.respond_to?(:constants)
272
+ end
273
+ end
274
+
275
+ # Sets an undefined constant for the duration of an example.
276
+ #
277
+ # @private
278
+ class UndefinedConstantSetter < BaseMutator
279
+ def mutate
280
+ @parent = @context_parts.inject(Object) do |klass, name|
281
+ if const_defined_on?(klass, name)
282
+ get_const_defined_on(klass, name)
283
+ else
284
+ ConstantMutator.stub(name_for(klass, name), Module.new)
285
+ end
286
+ end
287
+
288
+ @parent.const_set(@const_name, @mutated_value)
289
+ end
290
+
291
+ def to_constant
292
+ const = super
293
+ const.stubbed = true
294
+ const.previously_defined = false
295
+
296
+ const
297
+ end
298
+
299
+ def reset
300
+ @parent.__send__(:remove_const, @const_name)
301
+ end
302
+
303
+ private
304
+
305
+ def name_for(parent, name)
306
+ root = if parent == Object
307
+ ''
308
+ else
309
+ parent.name
310
+ end
311
+ root + '::' + name
312
+ end
313
+ end
314
+
315
+ # Uses the mutator to mutate (stub or hide) a constant. Ensures that
316
+ # the mutator is correctly registered so it can be backed out at the end
317
+ # of the test.
318
+ #
319
+ # @private
320
+ def self.mutate(mutator)
321
+ ::RSpec::Mocks.space.register_constant_mutator(mutator)
322
+ mutator.mutate
323
+ end
324
+
325
+ # Used internally by the constant stubbing to raise a helpful
326
+ # error when a constant like "A::B::C" is stubbed and A::B is
327
+ # not a module (and thus, it's impossible to define "A::B::C"
328
+ # since only modules can have nested constants).
329
+ #
330
+ # @api private
331
+ def self.raise_on_invalid_const
332
+ lambda do |const_name, failed_name|
333
+ raise "Cannot stub constant #{failed_name} on #{const_name} " \
334
+ "since #{const_name} is not a module."
335
+ end
336
+ end
337
+ end
338
+ end
339
+ end
@@ -0,0 +1,149 @@
1
+ module RSpec
2
+ module Mocks
3
+ # @private
4
+ class ObjectReference
5
+ # Returns an appropriate Object or Module reference based
6
+ # on the given argument.
7
+ def self.for(object_module_or_name, allow_direct_object_refs=false)
8
+ case object_module_or_name
9
+ when Module
10
+ if anonymous_module?(object_module_or_name)
11
+ DirectObjectReference.new(object_module_or_name)
12
+ else
13
+ # Use a `NamedObjectReference` if it has a name because this
14
+ # will use the original value of the constant in case it has
15
+ # been stubbed.
16
+ NamedObjectReference.new(name_of(object_module_or_name))
17
+ end
18
+ when String
19
+ NamedObjectReference.new(object_module_or_name)
20
+ else
21
+ if allow_direct_object_refs
22
+ DirectObjectReference.new(object_module_or_name)
23
+ else
24
+ raise ArgumentError,
25
+ "Module or String expected, got #{object_module_or_name.inspect}"
26
+ end
27
+ end
28
+ end
29
+
30
+ if Module.new.name.nil?
31
+ def self.anonymous_module?(mod)
32
+ !name_of(mod)
33
+ end
34
+ else # 1.8.7
35
+ def self.anonymous_module?(mod)
36
+ name_of(mod) == ""
37
+ end
38
+ end
39
+ private_class_method :anonymous_module?
40
+
41
+ def self.name_of(mod)
42
+ MODULE_NAME_METHOD.bind(mod).call
43
+ end
44
+ private_class_method :name_of
45
+
46
+ # @private
47
+ MODULE_NAME_METHOD = Module.instance_method(:name)
48
+ end
49
+
50
+ # An implementation of rspec-mocks' reference interface.
51
+ # Used when an object is passed to {ExampleMethods#object_double}, or
52
+ # an anonymous class or module is passed to {ExampleMethods#instance_double}
53
+ # or {ExampleMethods#class_double}.
54
+ # Represents a reference to that object.
55
+ # @see NamedObjectReference
56
+ class DirectObjectReference
57
+ # @param object [Object] the object to which this refers
58
+ def initialize(object)
59
+ @object = object
60
+ end
61
+
62
+ # @return [String] the object's description (via `#inspect`).
63
+ def description
64
+ @object.inspect
65
+ end
66
+
67
+ # Defined for interface parity with the other object reference
68
+ # implementations. Raises an `ArgumentError` to indicate that `as_stubbed_const`
69
+ # is invalid when passing an object argument to `object_double`.
70
+ def const_to_replace
71
+ raise ArgumentError,
72
+ "Can not perform constant replacement with an anonymous object."
73
+ end
74
+
75
+ # The target of the verifying double (the object itself).
76
+ #
77
+ # @return [Object]
78
+ def target
79
+ @object
80
+ end
81
+
82
+ # Always returns true for an object as the class is defined.
83
+ #
84
+ # @return [true]
85
+ def defined?
86
+ true
87
+ end
88
+
89
+ # Yields if the reference target is loaded, providing a generic mechanism
90
+ # to optionally run a bit of code only when a reference's target is
91
+ # loaded.
92
+ #
93
+ # This specific implementation always yields because direct references
94
+ # are always loaded.
95
+ #
96
+ # @yield [Object] the target of this reference.
97
+ def when_loaded
98
+ yield @object
99
+ end
100
+ end
101
+
102
+ # An implementation of rspec-mocks' reference interface.
103
+ # Used when a string is passed to {ExampleMethods#object_double},
104
+ # and when a string, named class or named module is passed to
105
+ # {ExampleMethods#instance_double}, or {ExampleMethods#class_double}.
106
+ # Represents a reference to the object named (via a constant lookup)
107
+ # by the string.
108
+ # @see DirectObjectReference
109
+ class NamedObjectReference
110
+ # @param const_name [String] constant name
111
+ def initialize(const_name)
112
+ @const_name = const_name
113
+ end
114
+
115
+ # @return [Boolean] true if the named constant is defined, false otherwise.
116
+ def defined?
117
+ !!object
118
+ end
119
+
120
+ # @return [String] the constant name to replace with a double.
121
+ def const_to_replace
122
+ @const_name
123
+ end
124
+ alias description const_to_replace
125
+
126
+ # @return [Object, nil] the target of the verifying double (the named object), or
127
+ # nil if it is not defined.
128
+ def target
129
+ object
130
+ end
131
+
132
+ # Yields if the reference target is loaded, providing a generic mechanism
133
+ # to optionally run a bit of code only when a reference's target is
134
+ # loaded.
135
+ #
136
+ # @yield [Object] the target object
137
+ def when_loaded
138
+ yield object if object
139
+ end
140
+
141
+ private
142
+
143
+ def object
144
+ return @object if defined?(@object)
145
+ @object = Constant.original(@const_name).original_value
146
+ end
147
+ end
148
+ end
149
+ end
@@ -3,37 +3,78 @@ module RSpec
3
3
  # @private
4
4
  class OrderGroup
5
5
  def initialize
6
- @ordering = Array.new
6
+ @expectations = []
7
+ @invocation_order = []
8
+ @index = 0
7
9
  end
8
10
 
9
11
  # @private
10
12
  def register(expectation)
11
- @ordering << expectation
13
+ @expectations << expectation
14
+ end
15
+
16
+ def invoked(message)
17
+ @invocation_order << message
12
18
  end
13
19
 
14
20
  # @private
15
21
  def ready_for?(expectation)
16
- @ordering.first == expectation
22
+ remaining_expectations.find(&:ordered?) == expectation
17
23
  end
18
24
 
19
25
  # @private
20
26
  def consume
21
- @ordering.shift
27
+ remaining_expectations.each_with_index do |expectation, index|
28
+ next unless expectation.ordered?
29
+
30
+ @index += index + 1
31
+ return expectation
32
+ end
33
+ nil
22
34
  end
23
35
 
24
36
  # @private
25
37
  def handle_order_constraint(expectation)
26
- return unless @ordering.include?(expectation)
38
+ return unless expectation.ordered? && remaining_expectations.include?(expectation)
27
39
  return consume if ready_for?(expectation)
28
40
  expectation.raise_out_of_order_error
29
41
  end
30
42
 
43
+ def verify_invocation_order(expectation)
44
+ expectation.raise_out_of_order_error unless expectations_invoked_in_order?
45
+ true
46
+ end
47
+
31
48
  def clear
32
- @ordering.clear
49
+ @index = 0
50
+ @invocation_order.clear
51
+ @expectations.clear
33
52
  end
34
53
 
35
54
  def empty?
36
- @ordering.empty?
55
+ @expectations.empty?
56
+ end
57
+
58
+ private
59
+
60
+ def remaining_expectations
61
+ @expectations[@index..-1] || []
62
+ end
63
+
64
+ def expectations_invoked_in_order?
65
+ invoked_expectations == expected_invocations
66
+ end
67
+
68
+ def invoked_expectations
69
+ @expectations.select { |e| e.ordered? && @invocation_order.include?(e) }
70
+ end
71
+
72
+ def expected_invocations
73
+ @invocation_order.map { |invocation| expectation_for(invocation) }.compact
74
+ end
75
+
76
+ def expectation_for(message)
77
+ @expectations.find { |e| message == e }
37
78
  end
38
79
  end
39
80
  end