rspec-mocks 3.0.4 → 3.12.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +512 -2
  6. data/{License.txt → LICENSE.md} +5 -4
  7. data/README.md +113 -30
  8. data/lib/rspec/mocks/any_instance/chain.rb +5 -3
  9. data/lib/rspec/mocks/any_instance/error_generator.rb +31 -0
  10. data/lib/rspec/mocks/any_instance/expect_chain_chain.rb +1 -5
  11. data/lib/rspec/mocks/any_instance/expectation_chain.rb +9 -8
  12. data/lib/rspec/mocks/any_instance/message_chains.rb +7 -8
  13. data/lib/rspec/mocks/any_instance/proxy.rb +14 -5
  14. data/lib/rspec/mocks/any_instance/recorder.rb +61 -31
  15. data/lib/rspec/mocks/any_instance/stub_chain.rb +15 -11
  16. data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +1 -5
  17. data/lib/rspec/mocks/any_instance.rb +1 -0
  18. data/lib/rspec/mocks/argument_list_matcher.rb +55 -10
  19. data/lib/rspec/mocks/argument_matchers.rb +88 -30
  20. data/lib/rspec/mocks/configuration.rb +61 -13
  21. data/lib/rspec/mocks/error_generator.rb +250 -107
  22. data/lib/rspec/mocks/example_methods.rb +151 -28
  23. data/lib/rspec/mocks/instance_method_stasher.rb +17 -6
  24. data/lib/rspec/mocks/matchers/have_received.rb +50 -20
  25. data/lib/rspec/mocks/matchers/receive.rb +39 -11
  26. data/lib/rspec/mocks/matchers/receive_message_chain.rb +22 -7
  27. data/lib/rspec/mocks/matchers/receive_messages.rb +12 -7
  28. data/lib/rspec/mocks/message_chain.rb +3 -7
  29. data/lib/rspec/mocks/message_expectation.rb +466 -307
  30. data/lib/rspec/mocks/method_double.rb +88 -29
  31. data/lib/rspec/mocks/method_reference.rb +85 -25
  32. data/lib/rspec/mocks/minitest_integration.rb +68 -0
  33. data/lib/rspec/mocks/mutate_const.rb +50 -109
  34. data/lib/rspec/mocks/object_reference.rb +89 -32
  35. data/lib/rspec/mocks/order_group.rb +4 -5
  36. data/lib/rspec/mocks/proxy.rb +156 -60
  37. data/lib/rspec/mocks/space.rb +52 -35
  38. data/lib/rspec/mocks/standalone.rb +1 -1
  39. data/lib/rspec/mocks/syntax.rb +26 -30
  40. data/lib/rspec/mocks/targets.rb +55 -28
  41. data/lib/rspec/mocks/test_double.rb +43 -7
  42. data/lib/rspec/mocks/verifying_double.rb +27 -33
  43. data/lib/rspec/mocks/{verifying_message_expecation.rb → verifying_message_expectation.rb} +11 -16
  44. data/lib/rspec/mocks/verifying_proxy.rb +77 -26
  45. data/lib/rspec/mocks/version.rb +1 -1
  46. data/lib/rspec/mocks.rb +8 -1
  47. data.tar.gz.sig +0 -0
  48. metadata +80 -43
  49. metadata.gz.sig +0 -0
@@ -1,85 +1,11 @@
1
+ RSpec::Support.require_rspec_support 'recursive_const_methods'
2
+
1
3
  module RSpec
2
4
  module Mocks
3
- # Provides recursive constant lookup methods useful for
4
- # constant stubbing.
5
- #
6
- # @private
7
- module RecursiveConstMethods
8
- # We only want to consider constants that are defined directly on a
9
- # particular module, and not include top-level/inherited constants.
10
- # Unfortunately, the constant API changed between 1.8 and 1.9, so
11
- # we need to conditionally define methods to ignore the top-level/inherited
12
- # constants.
13
- #
14
- # Given:
15
- # class A; B = 1; end
16
- # class C < A; end
17
- #
18
- # On 1.8:
19
- # - C.const_get("Hash") # => ::Hash
20
- # - C.const_defined?("Hash") # => false
21
- # - C.constants # => ["B"]
22
- # - None of these methods accept the extra `inherit` argument
23
- # On 1.9:
24
- # - C.const_get("Hash") # => ::Hash
25
- # - C.const_defined?("Hash") # => true
26
- # - C.const_get("Hash", false) # => raises NameError
27
- # - C.const_defined?("Hash", false) # => false
28
- # - C.constants # => [:B]
29
- # - C.constants(false) #=> []
30
- if Module.method(:const_defined?).arity == 1
31
- def const_defined_on?(mod, const_name)
32
- mod.const_defined?(const_name)
33
- end
34
-
35
- def get_const_defined_on(mod, const_name)
36
- if const_defined_on?(mod, const_name)
37
- return mod.const_get(const_name)
38
- end
39
-
40
- raise NameError, "uninitialized constant #{mod.name}::#{const_name}"
41
- end
42
-
43
- def constants_defined_on(mod)
44
- mod.constants.select { |c| const_defined_on?(mod, c) }
45
- end
46
- else
47
- def const_defined_on?(mod, const_name)
48
- mod.const_defined?(const_name, false)
49
- end
50
-
51
- def get_const_defined_on(mod, const_name)
52
- mod.const_get(const_name, false)
53
- end
54
-
55
- def constants_defined_on(mod)
56
- mod.constants(false)
57
- end
58
- end
59
-
60
- def recursive_const_get(const_name)
61
- normalize_const_name(const_name).split('::').inject(Object) do |mod, name|
62
- get_const_defined_on(mod, name)
63
- end
64
- end
65
-
66
- def recursive_const_defined?(const_name)
67
- normalize_const_name(const_name).split('::').inject([Object, '']) do |(mod, full_name), name|
68
- yield(full_name, name) if block_given? && !(Module === mod)
69
- return false unless const_defined_on?(mod, name)
70
- [get_const_defined_on(mod, name), [mod, name].join('::')]
71
- end
72
- end
73
-
74
- def normalize_const_name(const_name)
75
- const_name.sub(/\A::/, '')
76
- end
77
- end
78
-
79
5
  # Provides information about constants that may (or may not)
80
6
  # have been mutated by rspec-mocks.
81
7
  class Constant
82
- extend RecursiveConstMethods
8
+ extend Support::RecursiveConstMethods
83
9
 
84
10
  # @api private
85
11
  def initialize(name)
@@ -87,6 +13,8 @@ module RSpec
87
13
  @previously_defined = false
88
14
  @stubbed = false
89
15
  @hidden = false
16
+ @valid_name = true
17
+ yield self if block_given?
90
18
  end
91
19
 
92
20
  # @return [String] The fully qualified name of the constant.
@@ -98,7 +26,7 @@ module RSpec
98
26
  attr_accessor :original_value
99
27
 
100
28
  # @private
101
- attr_writer :previously_defined, :stubbed, :hidden
29
+ attr_writer :previously_defined, :stubbed, :hidden, :valid_name
102
30
 
103
31
  # @return [Boolean] Whether or not the constant was defined
104
32
  # before the current example.
@@ -124,6 +52,12 @@ module RSpec
124
52
  @hidden
125
53
  end
126
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
+
127
61
  # The default `to_s` isn't very useful, so a custom version is provided.
128
62
  def to_s
129
63
  "#<#{self.class.name} #{name}>"
@@ -132,19 +66,22 @@ module RSpec
132
66
 
133
67
  # @private
134
68
  def self.unmutated(name)
135
- const = new(name)
136
- const.previously_defined = recursive_const_defined?(name)
137
- const.stubbed = false
138
- const.hidden = false
139
- const.original_value = recursive_const_get(name) if const.previously_defined?
140
-
141
- const
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
142
79
  end
143
80
 
144
81
  # Queries rspec-mocks to find out information about the named constant.
145
82
  #
146
83
  # @param [String] name the name of the constant
147
- # @return [Constant] an object contaning information about the named
84
+ # @return [Constant] an object containing information about the named
148
85
  # constant.
149
86
  def self.original(name)
150
87
  mutator = ::RSpec::Mocks.space.constant_mutator_for(name)
@@ -154,7 +91,7 @@ module RSpec
154
91
 
155
92
  # Provides a means to stub constants.
156
93
  class ConstantMutator
157
- extend RecursiveConstMethods
94
+ extend Support::RecursiveConstMethods
158
95
 
159
96
  # Stubs a constant.
160
97
  #
@@ -167,13 +104,17 @@ module RSpec
167
104
  # examples. This is an alternate public API that is provided
168
105
  # so you can stub constants in other contexts (e.g. helper
169
106
  # classes).
170
- def self.stub(constant_name, value, options = {})
171
- mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
172
- DefinedConstantReplacer
173
- else
174
- UndefinedConstantSetter
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}"
175
110
  end
176
111
 
112
+ mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
113
+ DefinedConstantReplacer
114
+ else
115
+ UndefinedConstantSetter
116
+ end
117
+
177
118
  mutate(mutator.new(constant_name, value, options[:transfer_nested_constants]))
178
119
  value
179
120
  end
@@ -188,7 +129,7 @@ module RSpec
188
129
  # so you can hide constants in other contexts (e.g. helper
189
130
  # classes).
190
131
  def self.hide(constant_name)
191
- mutate(ConstantHider.new(constant_name, nil, { }))
132
+ mutate(ConstantHider.new(constant_name, nil, {}))
192
133
  nil
193
134
  end
194
135
 
@@ -196,7 +137,7 @@ module RSpec
196
137
  #
197
138
  # @private
198
139
  class BaseMutator
199
- include RecursiveConstMethods
140
+ include Support::RecursiveConstMethods
200
141
 
201
142
  attr_reader :original_value, :full_constant_name
202
143
 
@@ -227,7 +168,7 @@ module RSpec
227
168
  # @private
228
169
  class ConstantHider < BaseMutator
229
170
  def mutate
230
- return unless @defined = recursive_const_defined?(full_constant_name)
171
+ return unless (@defined = recursive_const_defined?(full_constant_name))
231
172
  @context = recursive_const_get(@context_parts.join('::'))
232
173
  @original_value = get_const_defined_on(@context, @const_name)
233
174
 
@@ -298,12 +239,12 @@ module RSpec
298
239
  return [] unless should_transfer_nested_constants?
299
240
 
300
241
  { @original_value => "the original value", @mutated_value => "the stubbed value" }.each do |value, description|
301
- unless value.respond_to?(:constants)
302
- raise ArgumentError,
303
- "Cannot transfer nested constants for #{@full_constant_name} " +
304
- "since #{description} is not a class or module and only classes " +
305
- "and modules support nested constants."
306
- end
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."
307
248
  end
308
249
 
309
250
  if Array === @transfer_nested_constants
@@ -313,9 +254,9 @@ module RSpec
313
254
  if undefined_constants.any?
314
255
  available_constants = constants_defined_on(@original_value) - @transfer_nested_constants
315
256
  raise ArgumentError,
316
- "Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " +
317
- "for #{@full_constant_name} since they are not defined. Did you mean " +
318
- "#{available_constants.join(' or ')}?"
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 ')}?"
319
260
  end
320
261
 
321
262
  @transfer_nested_constants
@@ -363,10 +304,10 @@ module RSpec
363
304
 
364
305
  def name_for(parent, name)
365
306
  root = if parent == Object
366
- ''
367
- else
368
- parent.name
369
- end
307
+ ''
308
+ else
309
+ parent.name
310
+ end
370
311
  root + '::' + name
371
312
  end
372
313
  end
@@ -389,7 +330,7 @@ module RSpec
389
330
  # @api private
390
331
  def self.raise_on_invalid_const
391
332
  lambda do |const_name, failed_name|
392
- raise "Cannot stub constant #{failed_name} on #{const_name} " +
333
+ raise "Cannot stub constant #{failed_name} on #{const_name} " \
393
334
  "since #{const_name} is not a module."
394
335
  end
395
336
  end
@@ -1,91 +1,148 @@
1
1
  module RSpec
2
2
  module Mocks
3
-
4
3
  # @private
5
4
  class ObjectReference
6
5
  # Returns an appropriate Object or Module reference based
7
6
  # on the given argument.
8
- def self.for(object_module_or_name, allow_direct_object_refs = false)
7
+ def self.for(object_module_or_name, allow_direct_object_refs=false)
9
8
  case object_module_or_name
10
- when Module then DirectModuleReference.new(object_module_or_name)
11
- when String then NamedObjectReference.new(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)
12
23
  else
13
- if allow_direct_object_refs
14
- DirectObjectReference.new(object_module_or_name)
15
- else
16
- raise ArgumentError,
17
- "Module or String expected, got #{object_module_or_name.inspect}"
18
- end
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) == ""
19
37
  end
20
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)
21
48
  end
22
49
 
23
- # Used when an object is passed to `object_double`.
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}.
24
54
  # Represents a reference to that object.
25
- #
26
- # @private
55
+ # @see NamedObjectReference
27
56
  class DirectObjectReference
57
+ # @param object [Object] the object to which this refers
28
58
  def initialize(object)
29
59
  @object = object
30
60
  end
31
61
 
62
+ # @return [String] the object's description (via `#inspect`).
32
63
  def description
33
64
  @object.inspect
34
65
  end
35
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`.
36
70
  def const_to_replace
37
71
  raise ArgumentError,
38
- "Can not perform constant replacement with an object."
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
39
80
  end
40
81
 
82
+ # Always returns true for an object as the class is defined.
83
+ #
84
+ # @return [true]
41
85
  def defined?
42
86
  true
43
87
  end
44
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.
45
97
  def when_loaded
46
98
  yield @object
47
99
  end
48
100
  end
49
101
 
50
- # Used when a module is passed to `class_double` or `instance_double`.
51
- # Represents a reference to that module.
52
- #
53
- # @private
54
- class DirectModuleReference < DirectObjectReference
55
- def const_to_replace
56
- @object.name
57
- end
58
- alias description const_to_replace
59
- end
60
-
61
- # Used when a string is passed to `class_double`, `instance_double`
62
- # or `object_double`.
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}.
63
106
  # Represents a reference to the object named (via a constant lookup)
64
107
  # by the string.
65
- #
66
- # @private
108
+ # @see DirectObjectReference
67
109
  class NamedObjectReference
110
+ # @param const_name [String] constant name
68
111
  def initialize(const_name)
69
112
  @const_name = const_name
70
113
  end
71
114
 
115
+ # @return [Boolean] true if the named constant is defined, false otherwise.
72
116
  def defined?
73
117
  !!object
74
118
  end
75
119
 
120
+ # @return [String] the constant name to replace with a double.
76
121
  def const_to_replace
77
122
  @const_name
78
123
  end
79
124
  alias description const_to_replace
80
125
 
81
- def when_loaded(&block)
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
82
138
  yield object if object
83
139
  end
84
140
 
85
- private
141
+ private
86
142
 
87
143
  def object
88
- @object ||= Constant.original(@const_name).original_value
144
+ return @object if defined?(@object)
145
+ @object = Constant.original(@const_name).original_value
89
146
  end
90
147
  end
91
148
  end
@@ -25,10 +25,10 @@ module RSpec
25
25
  # @private
26
26
  def consume
27
27
  remaining_expectations.each_with_index do |expectation, index|
28
- if expectation.ordered?
29
- @index += index + 1
30
- return expectation
31
- end
28
+ next unless expectation.ordered?
29
+
30
+ @index += index + 1
31
+ return expectation
32
32
  end
33
33
  nil
34
34
  end
@@ -76,7 +76,6 @@ module RSpec
76
76
  def expectation_for(message)
77
77
  @expectations.find { |e| message == e }
78
78
  end
79
-
80
79
  end
81
80
  end
82
81
  end