rspec-mocks 2.10.1 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. data/Changelog.md +26 -1
  2. data/README.md +14 -2
  3. data/features/stubbing_constants/README.md +62 -0
  4. data/features/stubbing_constants/stub_defined_constant.feature +79 -0
  5. data/features/stubbing_constants/stub_undefined_constant.feature +50 -0
  6. data/lib/rspec/mocks/any_instance.rb +37 -1
  7. data/lib/rspec/mocks/any_instance/chain.rb +0 -81
  8. data/lib/rspec/mocks/any_instance/expectation_chain.rb +57 -0
  9. data/lib/rspec/mocks/any_instance/recorder.rb +6 -1
  10. data/lib/rspec/mocks/any_instance/stub_chain.rb +37 -0
  11. data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +25 -0
  12. data/lib/rspec/mocks/argument_list_matcher.rb +93 -0
  13. data/lib/rspec/mocks/argument_matchers.rb +39 -31
  14. data/lib/rspec/mocks/error_generator.rb +7 -0
  15. data/lib/rspec/mocks/example_methods.rb +41 -0
  16. data/lib/rspec/mocks/framework.rb +2 -1
  17. data/lib/rspec/mocks/message_expectation.rb +21 -13
  18. data/lib/rspec/mocks/methods.rb +4 -0
  19. data/lib/rspec/mocks/proxy.rb +10 -4
  20. data/lib/rspec/mocks/stub_const.rb +280 -0
  21. data/lib/rspec/mocks/test_double.rb +3 -2
  22. data/lib/rspec/mocks/version.rb +1 -1
  23. data/spec/rspec/mocks/any_instance/message_chains_spec.rb +1 -1
  24. data/spec/rspec/mocks/any_instance_spec.rb +66 -26
  25. data/spec/rspec/mocks/argument_expectation_spec.rb +7 -7
  26. data/spec/rspec/mocks/at_least_spec.rb +14 -0
  27. data/spec/rspec/mocks/block_return_value_spec.rb +8 -0
  28. data/spec/rspec/mocks/mock_spec.rb +33 -20
  29. data/spec/rspec/mocks/multiple_return_value_spec.rb +2 -2
  30. data/spec/rspec/mocks/null_object_mock_spec.rb +22 -0
  31. data/spec/rspec/mocks/stub_chain_spec.rb +45 -45
  32. data/spec/rspec/mocks/stub_const_spec.rb +309 -0
  33. data/spec/rspec/mocks/stub_spec.rb +2 -2
  34. data/spec/spec_helper.rb +0 -40
  35. metadata +18 -6
  36. data/lib/rspec/mocks/argument_expectation.rb +0 -52
@@ -56,7 +56,12 @@ module RSpec
56
56
  def should_receive(method_name, &block)
57
57
  @expectation_set = true
58
58
  observe!(method_name)
59
- message_chains.add(method_name, ExpectationChain.new(method_name, &block))
59
+ message_chains.add(method_name, PositiveExpectationChain.new(method_name, &block))
60
+ end
61
+
62
+ def should_not_receive(method_name, &block)
63
+ observe!(method_name)
64
+ message_chains.add(method_name, NegativeExpectationChain.new(method_name, &block))
60
65
  end
61
66
 
62
67
  # Removes any previously recorded stubs, stub_chains or message
@@ -0,0 +1,37 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ # @private
5
+ class StubChain < Chain
6
+
7
+ # @private
8
+ def initialize(*args, &block)
9
+ record(:stub, *args, &block)
10
+ end
11
+
12
+ # @private
13
+ def expectation_fulfilled?
14
+ true
15
+ end
16
+
17
+ private
18
+
19
+ def invocation_order
20
+ @invocation_order ||= {
21
+ :stub => [nil],
22
+ :with => [:stub],
23
+ :and_return => [:with, :stub],
24
+ :and_raise => [:with, :stub],
25
+ :and_yield => [:with, :stub]
26
+ }
27
+ end
28
+
29
+ def verify_invocation_order(rspec_method_name, *args, &block)
30
+ unless invocation_order[rspec_method_name].include?(last_message)
31
+ raise(NoMethodError, "Undefined method #{rspec_method_name}")
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ # @private
5
+ class StubChainChain < StubChain
6
+
7
+ # @private
8
+ def initialize(*args, &block)
9
+ record(:stub_chain, *args, &block)
10
+ end
11
+
12
+ private
13
+
14
+ def invocation_order
15
+ @invocation_order ||= {
16
+ :stub_chain => [nil],
17
+ :and_return => [:stub_chain],
18
+ :and_raise => [:stub_chain],
19
+ :and_yield => [:stub_chain]
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,93 @@
1
+ require 'rspec/mocks/argument_matchers'
2
+
3
+ module RSpec
4
+ module Mocks
5
+ # Wrapper for matching arguments against a list of expected values. Used by
6
+ # the `with` method on a `MessageExpectation`:
7
+ #
8
+ # object.should_receive(:message).with(:a, 'b', 3)
9
+ # object.message(:a, 'b', 3)
10
+ #
11
+ # Values passed to `with` can be literal values or argument matchers that
12
+ # match against the real objects .e.g.
13
+ #
14
+ # object.should_receive(:message).with(hash_including(:a => 'b'))
15
+ #
16
+ # Can also be used directly to match the contents of any `Array`. This
17
+ # enables 3rd party mocking libs to take advantage of rspec's argument
18
+ # matching without using the rest of rspec-mocks.
19
+ #
20
+ # require 'rspec/mocks/argument_list_matcher'
21
+ # include RSpec::Mocks::ArgumentMatchers
22
+ #
23
+ # arg_list_matcher = RSpec::Mocks::ArgumentListMatcher.new(123, hash_including(:a => 'b'))
24
+ # arg_list_matcher.args_match?(123, :a => 'b')
25
+ #
26
+ # @see ArgumentMatchers
27
+ class ArgumentListMatcher
28
+ # @private
29
+ attr_reader :expected_args
30
+
31
+ # @api public
32
+ # @param [Array] *expected_args a list of expected literals and/or argument matchers
33
+ # @param [Block] block a block with arity matching the expected
34
+ #
35
+ # Initializes an `ArgumentListMatcher` with a collection of literal
36
+ # values and/or argument matchers, or a block that handles the evaluation
37
+ # for you.
38
+ #
39
+ # @see ArgumentMatchers
40
+ # @see #args_match?
41
+ def initialize(*expected_args, &block)
42
+ @expected_args = expected_args
43
+ @block = expected_args.empty? ? block : nil
44
+ @match_any_args = false
45
+ @matchers = nil
46
+
47
+ case expected_args.first
48
+ when ArgumentMatchers::AnyArgsMatcher
49
+ @match_any_args = true
50
+ when ArgumentMatchers::NoArgsMatcher
51
+ @matchers = []
52
+ else
53
+ @matchers = expected_args.collect {|arg| matcher_for(arg)}
54
+ end
55
+ end
56
+
57
+ # @api public
58
+ # @param [Array] *args
59
+ #
60
+ # Matches each element in the `expected_args` against the element in the same
61
+ # position of the arguments passed to `new`.
62
+ #
63
+ # @see #initialize
64
+ def args_match?(*args)
65
+ match_any_args? || block_passes?(*args) || matchers_match?(*args)
66
+ end
67
+
68
+ private
69
+
70
+ def matcher_for(arg)
71
+ return ArgumentMatchers::MatcherMatcher.new(arg) if is_matcher?(arg)
72
+ return ArgumentMatchers::RegexpMatcher.new(arg) if arg.is_a?(Regexp)
73
+ return ArgumentMatchers::EqualityProxy.new(arg)
74
+ end
75
+
76
+ def is_matcher?(obj)
77
+ !obj.null_object? & obj.respond_to?(:matches?) & [:failure_message_for_should, :failure_message].any? { |m| obj.respond_to?(m) }
78
+ end
79
+
80
+ def block_passes?(*args)
81
+ @block.call(*args) if @block
82
+ end
83
+
84
+ def matchers_match?(*args)
85
+ @matchers == args
86
+ end
87
+
88
+ def match_any_args?
89
+ @match_any_args
90
+ end
91
+ end
92
+ end
93
+ end
@@ -7,6 +7,8 @@ module RSpec
7
7
  #
8
8
  # With the exception of `any_args` and `no_args`, they all match against
9
9
  # the arg in same position in the argument list.
10
+ #
11
+ # @see ArgumentListMatcher
10
12
  module ArgumentMatchers
11
13
 
12
14
  class AnyArgsMatcher
@@ -64,7 +66,7 @@ module RSpec
64
66
  "hash_including(#{@expected.inspect.sub(/^\{/,"").sub(/\}$/,"")})"
65
67
  end
66
68
  end
67
-
69
+
68
70
  class HashExcludingMatcher
69
71
  def initialize(expected)
70
72
  @expected = expected
@@ -80,7 +82,7 @@ module RSpec
80
82
  "hash_not_including(#{@expected.inspect.sub(/^\{/,"").sub(/\}$/,"")})"
81
83
  end
82
84
  end
83
-
85
+
84
86
  class DuckTypeMatcher
85
87
  def initialize(*methods_to_respond_to)
86
88
  @methods_to_respond_to = methods_to_respond_to
@@ -110,47 +112,47 @@ module RSpec
110
112
  @given == expected
111
113
  end
112
114
  end
113
-
115
+
114
116
  class InstanceOf
115
117
  def initialize(klass)
116
118
  @klass = klass
117
119
  end
118
-
120
+
119
121
  def ==(actual)
120
122
  actual.instance_of?(@klass)
121
123
  end
122
124
  end
123
-
125
+
124
126
  class KindOf
125
127
  def initialize(klass)
126
128
  @klass = klass
127
129
  end
128
-
130
+
129
131
  def ==(actual)
130
132
  actual.kind_of?(@klass)
131
133
  end
132
134
  end
133
135
 
134
- # Passes if object receives `:message` with any args at all. This is
135
- # really a more explicit variation of `object.should_receive(:message)`
136
+ # Matches any args at all. Supports a more explicit variation of
137
+ # `object.should_receive(:message)`
136
138
  #
137
139
  # @example
138
140
  #
139
- # object.should_receive(:message).with(any_args())
141
+ # object.should_receive(:message).with(any_args)
140
142
  def any_args
141
143
  AnyArgsMatcher.new
142
144
  end
143
-
144
- # Passes as long as there is an argument.
145
+
146
+ # Matches any argument at all.
145
147
  #
146
148
  # @example
147
149
  #
148
- # object.should_receive(:message).with(anything())
150
+ # object.should_receive(:message).with(anything)
149
151
  def anything
150
152
  AnyArgMatcher.new(nil)
151
153
  end
152
-
153
- # Passes if no arguments are passed along with the message
154
+
155
+ # Matches no arguments.
154
156
  #
155
157
  # @example
156
158
  #
@@ -158,8 +160,8 @@ module RSpec
158
160
  def no_args
159
161
  NoArgsMatcher.new
160
162
  end
161
-
162
- # Passes if the argument responds to the specified messages.
163
+
164
+ # Matches if the actual argument responds to the specified messages.
163
165
  #
164
166
  # @example
165
167
  #
@@ -169,7 +171,7 @@ module RSpec
169
171
  DuckTypeMatcher.new(*args)
170
172
  end
171
173
 
172
- # Passes if the argument is boolean.
174
+ # Matches a boolean value.
173
175
  #
174
176
  # @example
175
177
  #
@@ -177,9 +179,9 @@ module RSpec
177
179
  def boolean
178
180
  BooleanMatcher.new(nil)
179
181
  end
180
-
181
- # Passes if the argument is a hash that includes the specified key(s) or
182
- # key/value pairs. If the hash includes other keys, it will still pass.
182
+
183
+ # Matches a hash that includes the specified key(s) or key/value pairs.
184
+ # Ignores any additional keys.
183
185
  #
184
186
  # @example
185
187
  #
@@ -189,9 +191,8 @@ module RSpec
189
191
  def hash_including(*args)
190
192
  HashIncludingMatcher.new(anythingize_lonely_keys(*args))
191
193
  end
192
-
193
- # Passes if the argument is a hash that doesn't include the specified
194
- # key(s) or key/value
194
+
195
+ # Matches a hash that doesn't include the specified key(s) or key/value.
195
196
  #
196
197
  # @example
197
198
  #
@@ -203,23 +204,30 @@ module RSpec
203
204
  end
204
205
 
205
206
  alias_method :hash_not_including, :hash_excluding
206
-
207
- # Passes if `arg.instance_of?(klass)`
207
+
208
+ # Matches if `arg.instance_of?(klass)`
209
+ #
210
+ # @example
211
+ #
212
+ # object.should_receive(:message).with(instance_of(Thing))
208
213
  def instance_of(klass)
209
214
  InstanceOf.new(klass)
210
215
  end
211
-
216
+
212
217
  alias_method :an_instance_of, :instance_of
213
-
214
- # Passes if `arg.kind_of?(klass)`
218
+
219
+ # Matches if `arg.kind_of?(klass)`
220
+ # @example
221
+ #
222
+ # object.should_receive(:message).with(kind_of(Thing))
215
223
  def kind_of(klass)
216
224
  KindOf.new(klass)
217
225
  end
218
-
226
+
219
227
  alias_method :a_kind_of, :kind_of
220
-
228
+
221
229
  private
222
-
230
+
223
231
  def anythingize_lonely_keys(*args)
224
232
  hash = args.last.class == Hash ? args.delete_at(-1) : {}
225
233
  args.each { | arg | hash[arg] = anything }
@@ -27,6 +27,13 @@ module RSpec
27
27
  __raise "#{intro} received #{expectation.message.inspect} with unexpected arguments\n expected: #{expected_args}\n got: #{actual_args}"
28
28
  end
29
29
 
30
+ # @private
31
+ def raise_missing_default_stub_error(expectation,*args)
32
+ expected_args = format_args(*expectation.expected_args)
33
+ actual_args = args.collect {|a| format_args(*a)}.join(", ")
34
+ __raise "#{intro} received #{expectation.message.inspect} with unexpected arguments\n Please stub a default value first if message might be received with other args as well. \n"
35
+ end
36
+
30
37
  # @private
31
38
  def raise_similar_message_args_error(expectation, *args)
32
39
  expected_args = format_args(*expectation.expected_args)
@@ -41,6 +41,47 @@ module RSpec
41
41
  Proxy.allow_message_expectations_on_nil
42
42
  end
43
43
 
44
+ # Stubs the named constant with the given value.
45
+ # Like method stubs, the constant will be restored
46
+ # to its original value (or lack of one, if it was
47
+ # undefined) when the example completes.
48
+ #
49
+ # @param constant_name [String] The fully qualified name of the constant. The current
50
+ # constant scoping at the point of call is not considered.
51
+ # @param value [Object] The value to make the constant refer to. When the
52
+ # example completes, the constant will be restored to its prior state.
53
+ # @param options [Hash] Stubbing options.
54
+ # @option options :transfer_nested_constants [Boolean, Array<Symbol>] Determines
55
+ # what nested constants, if any, will be transferred from the original value
56
+ # of the constant to the new value of the constant. This only works if both
57
+ # the original and new values are modules (or classes).
58
+ # @return [Object] the stubbed value of the constant
59
+ #
60
+ # @example
61
+ #
62
+ # stub_const("MyClass", Class.new) # => Replaces (or defines) MyClass with a new class object.
63
+ # stub_const("SomeModel::PER_PAGE", 5) # => Sets SomeModel::PER_PAGE to 5.
64
+ #
65
+ # class CardDeck
66
+ # SUITS = [:Spades, :Diamonds, :Clubs, :Hearts]
67
+ # NUM_CARDS = 52
68
+ # end
69
+ #
70
+ # stub_const("CardDeck", Class.new)
71
+ # CardDeck::SUITS # => uninitialized constant error
72
+ # CardDeck::NUM_CARDS # => uninitialized constant error
73
+ #
74
+ # stub_const("CardDeck", Class.new, :transfer_nested_constants => true)
75
+ # CardDeck::SUITS # => our suits array
76
+ # CardDeck::NUM_CARDS # => 52
77
+ #
78
+ # stub_const("CardDeck", Class.new, :transfer_nested_constants => [:SUITS])
79
+ # CardDeck::SUITS # => our suits array
80
+ # CardDeck::NUM_CARDS # => uninitialized constant error
81
+ def stub_const(constant_name, value, options = {})
82
+ ConstantStubber.stub(constant_name, value, options)
83
+ end
84
+
44
85
  private
45
86
 
46
87
  def declare_double(declared_as, *args)
@@ -9,7 +9,7 @@ require 'rspec/mocks/argument_matchers'
9
9
  require 'rspec/mocks/proxy'
10
10
  require 'rspec/mocks/test_double'
11
11
  require 'rspec/mocks/mock'
12
- require 'rspec/mocks/argument_expectation'
12
+ require 'rspec/mocks/argument_list_matcher'
13
13
  require 'rspec/mocks/message_expectation'
14
14
  require 'rspec/mocks/order_group'
15
15
  require 'rspec/mocks/errors'
@@ -17,3 +17,4 @@ require 'rspec/mocks/error_generator'
17
17
  require 'rspec/mocks/space'
18
18
  require 'rspec/mocks/serialization'
19
19
  require 'rspec/mocks/any_instance'
20
+ require 'rspec/mocks/stub_const'
@@ -3,11 +3,10 @@ module RSpec
3
3
 
4
4
  class MessageExpectation
5
5
  # @private
6
- attr_reader :message
7
- attr_writer :expected_received_count, :expected_from, :argument_expectation, :implementation
8
- protected :expected_received_count=, :expected_from=, :implementation=
9
6
  attr_accessor :error_generator
10
- protected :error_generator, :error_generator=
7
+ attr_reader :message
8
+ attr_writer :expected_received_count, :expected_from, :argument_list_matcher
9
+ protected :expected_received_count=, :expected_from=, :error_generator, :error_generator=
11
10
 
12
11
  # @private
13
12
  def initialize(error_generator, expectation_ordering, expected_from, message, expected_received_count=1, opts={}, &implementation)
@@ -17,7 +16,7 @@ module RSpec
17
16
  @message = message
18
17
  @actual_received_count = 0
19
18
  @expected_received_count = expected_received_count
20
- @argument_expectation = ArgumentExpectation.new(ArgumentMatchers::AnyArgsMatcher.new)
19
+ @argument_list_matcher = ArgumentListMatcher.new(ArgumentMatchers::AnyArgsMatcher.new)
21
20
  @consecutive = false
22
21
  @exception_to_raise = nil
23
22
  @args_to_throw = []
@@ -30,6 +29,12 @@ module RSpec
30
29
  @implementation = implementation
31
30
  end
32
31
 
32
+ def implementation=(implementation)
33
+ @consecutive = false
34
+ @implementation = implementation
35
+ end
36
+ protected :implementation=
37
+
33
38
  # @private
34
39
  def build_child(expected_from, expected_received_count, opts={}, &implementation)
35
40
  child = clone
@@ -41,13 +46,13 @@ module RSpec
41
46
  new_gen.opts = opts
42
47
  child.error_generator = new_gen
43
48
  child.clone_args_to_yield(*@args_to_yield)
44
- child.argument_expectation = ArgumentExpectation.new(ArgumentMatchers::AnyArgsMatcher.new)
49
+ child.argument_list_matcher = ArgumentListMatcher.new(ArgumentMatchers::AnyArgsMatcher.new)
45
50
  child
46
51
  end
47
52
 
48
53
  # @private
49
54
  def expected_args
50
- @argument_expectation.args
55
+ @argument_list_matcher.expected_args
51
56
  end
52
57
 
53
58
  # @overload and_return(value)
@@ -110,7 +115,7 @@ module RSpec
110
115
  # car.stub(:go).and_raise
111
116
  # car.stub(:go).and_raise(OutOfGas)
112
117
  # car.stub(:go).and_raise(OutOfGas.new(2, :oz))
113
- def and_raise(exception=Exception)
118
+ def and_raise(exception=RuntimeError)
114
119
  @exception_to_raise = exception
115
120
  end
116
121
 
@@ -148,7 +153,7 @@ module RSpec
148
153
 
149
154
  # @private
150
155
  def matches?(message, *args)
151
- @message == message && @argument_expectation.args_match?(*args)
156
+ @message == message && @argument_list_matcher.args_match?(*args)
152
157
  end
153
158
 
154
159
  # @private
@@ -194,12 +199,15 @@ MESSAGE
194
199
 
195
200
  # @private
196
201
  def called_max_times?
197
- @expected_received_count != :any && @expected_received_count > 0 && @actual_received_count >= @expected_received_count
202
+ @expected_received_count != :any &&
203
+ !@at_least &&
204
+ @expected_received_count > 0 &&
205
+ @actual_received_count >= @expected_received_count
198
206
  end
199
207
 
200
208
  # @private
201
209
  def matches_name_but_not_args(message, *args)
202
- @message == message and not @argument_expectation.args_match?(*args)
210
+ @message == message and not @argument_list_matcher.args_match?(*args)
203
211
  end
204
212
 
205
213
  # @private
@@ -248,7 +256,7 @@ MESSAGE
248
256
  # @private
249
257
  def generate_error
250
258
  if similar_messages.empty?
251
- @error_generator.raise_expectation_error(@message, @expected_received_count, @actual_received_count, *@argument_expectation.args)
259
+ @error_generator.raise_expectation_error(@message, @expected_received_count, @actual_received_count, *expected_args)
252
260
  else
253
261
  @error_generator.raise_similar_message_args_error(self, *@similar_messages)
254
262
  end
@@ -284,7 +292,7 @@ MESSAGE
284
292
  # # => passes
285
293
  def with(*args, &block)
286
294
  @implementation = block if block_given? unless args.empty?
287
- @argument_expectation = ArgumentExpectation.new(*args, &block)
295
+ @argument_list_matcher = ArgumentListMatcher.new(*args, &block)
288
296
  self
289
297
  end
290
298