rspec-mocks 2.5.0 → 2.6.0.rc2

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 (50) hide show
  1. data/.travis.yml +7 -0
  2. data/Gemfile +13 -12
  3. data/Guardfile +3 -3
  4. data/README.md +2 -0
  5. data/features/.nav +4 -0
  6. data/features/Changelog.md +12 -0
  7. data/features/README.markdown +44 -4
  8. data/features/message_expectations/any_instance.feature +19 -0
  9. data/features/message_expectations/block_local_expectations.feature.pending +2 -2
  10. data/features/message_expectations/expect_message.feature +3 -3
  11. data/features/message_expectations/warn_when_expectation_is_set_on_nil.feature +3 -3
  12. data/features/method_stubs/README.md +32 -24
  13. data/features/method_stubs/any_instance.feature +23 -0
  14. data/features/method_stubs/as_null_object.feature +36 -0
  15. data/features/method_stubs/simple_return_value.feature +2 -2
  16. data/features/method_stubs/stub_chain.feature +13 -6
  17. data/features/method_stubs/stub_implementation.feature +1 -1
  18. data/features/method_stubs/to_ary.feature +45 -0
  19. data/features/outside_rspec/configuration.feature +3 -3
  20. data/features/outside_rspec/standalone.feature +2 -2
  21. data/features/step_definitions/additional_cli_steps.rb +4 -0
  22. data/features/support/env.rb +5 -1
  23. data/lib/rspec/mocks.rb +2 -1
  24. data/lib/rspec/mocks/any_instance.rb +246 -0
  25. data/lib/rspec/mocks/extensions/psych.rb +23 -0
  26. data/lib/rspec/mocks/framework.rb +1 -0
  27. data/lib/rspec/mocks/message_expectation.rb +7 -4
  28. data/lib/rspec/mocks/methods.rb +1 -1
  29. data/lib/rspec/mocks/mock.rb +2 -2
  30. data/lib/rspec/mocks/serialization.rb +5 -3
  31. data/lib/rspec/mocks/space.rb +1 -1
  32. data/lib/rspec/mocks/version.rb +1 -1
  33. data/spec/rspec/mocks/and_yield_spec.rb +3 -3
  34. data/spec/rspec/mocks/any_instance_spec.rb +578 -0
  35. data/spec/rspec/mocks/any_number_of_times_spec.rb +22 -28
  36. data/spec/rspec/mocks/at_least_spec.rb +6 -0
  37. data/spec/rspec/mocks/at_most_spec.rb +6 -0
  38. data/spec/rspec/mocks/bug_report_10263_spec.rb +12 -14
  39. data/spec/rspec/mocks/bug_report_7611_spec.rb +2 -4
  40. data/spec/rspec/mocks/failing_argument_matchers_spec.rb +45 -46
  41. data/spec/rspec/mocks/nil_expectation_warning_spec.rb +1 -2
  42. data/spec/rspec/mocks/passing_argument_matchers_spec.rb +119 -122
  43. data/spec/rspec/mocks/precise_counts_spec.rb +6 -0
  44. data/spec/rspec/mocks/serialization_spec.rb +21 -3
  45. data/spec/rspec/mocks/stub_chain_spec.rb +72 -37
  46. data/spec/rspec/mocks/stub_spec.rb +64 -61
  47. data/spec/rspec/mocks/to_ary_spec.rb +31 -0
  48. metadata +32 -15
  49. data/spec/rspec/mocks/bug_report_7805_spec.rb +0 -22
  50. data/spec/rspec/mocks/bug_report_8302_spec.rb +0 -26
@@ -0,0 +1,45 @@
1
+ Feature: double handling to_ary
2
+
3
+ Ruby implicitly sends to_ary to any objects in an Array when the array
4
+ receives `flatten`:
5
+
6
+ [obj].flatten # => obj.to_ary
7
+
8
+ To support this, an RSpec double will raise a NoMethodError when it receives
9
+ `to_ary`, unless that method is explicitly stubbed.
10
+
11
+ Scenario: double receiving to_ary
12
+ Given a file named "example.rb" with:
13
+ """
14
+ describe "a double receiving to_ary" do
15
+ shared_examples "to_ary" do
16
+ it "returns nil" do
17
+ expect do
18
+ obj.to_ary.should be_nil
19
+ end.to raise_error(NoMethodError)
20
+ end
21
+
22
+ it "can be overridden with a stub" do
23
+ obj.stub(:to_ary) { :non_nil_value }
24
+ obj.to_ary.should be(:non_nil_value)
25
+ end
26
+
27
+ it "supports Array#flatten" do
28
+ obj = double('foo')
29
+ [obj].flatten.should eq([obj])
30
+ end
31
+ end
32
+
33
+ context "double as_null_object" do
34
+ let(:obj) { double('obj').as_null_object }
35
+ include_examples "to_ary"
36
+ end
37
+
38
+ context "double without as_null_object" do
39
+ let(:obj) { double('obj') }
40
+ include_examples "to_ary"
41
+ end
42
+ end
43
+ """
44
+ When I run `rspec example.rb`
45
+ Then the examples should all pass
@@ -42,7 +42,7 @@ Feature: configure any test framework to use rspec-mocks
42
42
  puts example.respond_to?(:stub)
43
43
  """
44
44
 
45
- When I run "ruby foo.rb"
45
+ When I run `ruby foo.rb`
46
46
  Then the output should contain "true"
47
47
  But the output should not contain "false"
48
48
 
@@ -60,7 +60,7 @@ Feature: configure any test framework to use rspec-mocks
60
60
  puts obj.respond_to?(:stub)
61
61
  """
62
62
 
63
- When I run "ruby foo.rb"
63
+ When I run `ruby foo.rb`
64
64
  Then the output should contain "true"
65
65
  But the output should not contain "false"
66
66
 
@@ -76,7 +76,7 @@ Feature: configure any test framework to use rspec-mocks
76
76
  puts obj.respond_to?(:stub)
77
77
  """
78
78
 
79
- When I run "ruby foo.rb"
79
+ When I run `ruby foo.rb`
80
80
  Then the output should contain "false"
81
81
  But the output should not contain "true"
82
82
 
@@ -13,7 +13,7 @@ Feature: standalone
13
13
  greeter.stub(:say_hi) { "Hello!" }
14
14
  puts greeter.say_hi
15
15
  """
16
- When I run "ruby example.rb"
16
+ When I run `ruby example.rb`
17
17
  Then the output should contain "Hello!"
18
18
 
19
19
  Scenario: message expectation outside rspec
@@ -26,7 +26,7 @@ Feature: standalone
26
26
 
27
27
  RSpec::Mocks.verify
28
28
  """
29
- When I run "ruby example.rb"
29
+ When I run `ruby example.rb`
30
30
  Then the output should contain "say_hi(any args) (RSpec::Mocks::MockExpectationError)"
31
31
  Then the output should contain "expected: 1 time"
32
32
  Then the output should contain "received: 0 times"
@@ -0,0 +1,4 @@
1
+ Then /^the example(?:s)? should(?: all)? pass$/ do
2
+ Then %q{the output should contain "0 failures"}
3
+ Then %q{the exit status should be 0}
4
+ end
@@ -1,2 +1,6 @@
1
- require 'aruba'
1
+ require 'aruba/cucumber'
2
2
  require 'rspec/expectations'
3
+
4
+ Before do
5
+ @aruba_timeout_seconds = 3
6
+ end
@@ -177,7 +177,8 @@ module RSpec
177
177
  attr_accessor :space
178
178
 
179
179
  def setup(includer)
180
- Object.class_eval { include RSpec::Mocks::Methods } unless Object < RSpec::Mocks::Methods
180
+ Object.class_eval { include RSpec::Mocks::Methods }
181
+ Class.class_eval { include RSpec::Mocks::AnyInstance }
181
182
  (class << includer; self; end).class_eval do
182
183
  include RSpec::Mocks::ExampleMethods
183
184
  end
@@ -0,0 +1,246 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ class Chain
5
+ [
6
+ :with, :and_return, :and_raise, :and_yield,
7
+ :once, :twice, :any_number_of_times,
8
+ :exactly, :times, :never,
9
+ :at_least, :at_most
10
+ ].each do |method_name|
11
+ class_eval(<<-EOM, __FILE__, __LINE__)
12
+ def #{method_name}(*args, &block)
13
+ record(:#{method_name}, *args, &block)
14
+ end
15
+ EOM
16
+ end
17
+
18
+ def playback!(instance)
19
+ messages.inject(instance) do |instance, message|
20
+ instance.send(*message.first, &message.last)
21
+ end
22
+ end
23
+
24
+ def constrained_to_any_of?(*constraints)
25
+ constraints.any? do |constraint|
26
+ messages.any? do |message|
27
+ message.first.first == constraint
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+ def messages
34
+ @messages ||= []
35
+ end
36
+
37
+ def last_message
38
+ messages.last.first.first unless messages.empty?
39
+ end
40
+
41
+ def record(rspec_method_name, *args, &block)
42
+ verify_invocation_order(rspec_method_name, *args, &block)
43
+ messages << [args.unshift(rspec_method_name), block]
44
+ self
45
+ end
46
+ end
47
+
48
+ class StubChain < Chain
49
+ def initialize(*args, &block)
50
+ record(:stub, *args, &block)
51
+ end
52
+
53
+ def invocation_order
54
+ @invocation_order ||= {
55
+ :stub => [nil],
56
+ :with => [:stub],
57
+ :and_return => [:with, :stub],
58
+ :and_raise => [:with, :stub],
59
+ :and_yield => [:with, :stub]
60
+ }
61
+ end
62
+
63
+ def expectation_filfilled?
64
+ true
65
+ end
66
+
67
+ private
68
+ def verify_invocation_order(rspec_method_name, *args, &block)
69
+ unless invocation_order[rspec_method_name].include?(last_message)
70
+ raise(NoMethodError, "Undefined method #{rspec_method_name}")
71
+ end
72
+ end
73
+ end
74
+
75
+ class ExpectationChain < Chain
76
+ def initialize(*args, &block)
77
+ record(:should_receive, *args, &block)
78
+ @expectation_fulfilled = false
79
+ end
80
+
81
+ def invocation_order
82
+ @invocation_order ||= {
83
+ :should_receive => [nil],
84
+ :with => [:should_receive],
85
+ :and_return => [:with, :should_receive],
86
+ :and_raise => [:with, :should_receive]
87
+ }
88
+ end
89
+
90
+ def expectation_fulfilled!
91
+ @expectation_fulfilled = true
92
+ end
93
+
94
+ def expectation_filfilled?
95
+ @expectation_fulfilled || constrained_to_any_of?(:never, :any_number_of_times)
96
+ end
97
+
98
+ private
99
+ def verify_invocation_order(rspec_method_name, *args, &block)
100
+ end
101
+ end
102
+
103
+ class Recorder
104
+ def initialize(klass)
105
+ @message_chains = {}
106
+ @observed_methods = []
107
+ @played_methods = {}
108
+ @klass = klass
109
+ @expectation_set = false
110
+ end
111
+
112
+ def stub(method_name, *args, &block)
113
+ observe!(method_name)
114
+ @message_chains[method_name] = StubChain.new(method_name, *args, &block)
115
+ end
116
+
117
+ def should_receive(method_name, *args, &block)
118
+ observe!(method_name)
119
+ @expectation_set = true
120
+ @message_chains[method_name] = ExpectationChain.new(method_name, *args, &block)
121
+ end
122
+
123
+ def stop_observing!
124
+ @observed_methods.each do |method_name|
125
+ restore_method!(method_name)
126
+ end
127
+ end
128
+
129
+ def playback!(instance, method_name)
130
+ RSpec::Mocks::space.add(instance)
131
+ @message_chains[method_name].playback!(instance)
132
+ @played_methods[method_name] = instance
133
+ received_expected_message!(method_name) if has_expectation?(method_name)
134
+ end
135
+
136
+ def instance_that_received(method_name)
137
+ @played_methods[method_name]
138
+ end
139
+
140
+ def verify
141
+ if @expectation_set && !each_expectation_filfilled?
142
+ raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}"
143
+ end
144
+ end
145
+
146
+ private
147
+ def each_expectation_filfilled?
148
+ @message_chains.all? do |method_name, chain|
149
+ chain.expectation_filfilled?
150
+ end
151
+ end
152
+
153
+ def has_expectation?(method_name)
154
+ @message_chains[method_name].is_a?(ExpectationChain)
155
+ end
156
+
157
+ def unfulfilled_expectations
158
+ @message_chains.map do |method_name, chain|
159
+ method_name.to_s if chain.is_a?(ExpectationChain) unless chain.expectation_filfilled?
160
+ end.compact
161
+ end
162
+
163
+ def received_expected_message!(method_name)
164
+ @message_chains[method_name].expectation_fulfilled!
165
+ restore_method!(method_name)
166
+ mark_invoked!(method_name)
167
+ end
168
+
169
+ def restore_method!(method_name)
170
+ if @klass.method_defined?(build_alias_method_name(method_name))
171
+ restore_original_method!(method_name)
172
+ else
173
+ remove_dummy_method!(method_name)
174
+ end
175
+ end
176
+
177
+ def build_alias_method_name(method_name)
178
+ "__#{method_name}_without_any_instance__"
179
+ end
180
+
181
+ def restore_original_method!(method_name)
182
+ alias_method_name = build_alias_method_name(method_name)
183
+ @klass.class_eval do
184
+ alias_method method_name, alias_method_name
185
+ remove_method alias_method_name
186
+ end
187
+ end
188
+
189
+ def remove_dummy_method!(method_name)
190
+ @klass.class_eval do
191
+ remove_method method_name
192
+ end
193
+ end
194
+
195
+ def backup_method!(method_name)
196
+ alias_method_name = build_alias_method_name(method_name)
197
+ @klass.class_eval do
198
+ if method_defined?(method_name)
199
+ alias_method alias_method_name, method_name
200
+ end
201
+ end
202
+ end
203
+
204
+ def observe!(method_name)
205
+ @observed_methods << method_name
206
+ backup_method!(method_name)
207
+ @klass.class_eval(<<-EOM, __FILE__, __LINE__)
208
+ def #{method_name}(*args, &blk)
209
+ self.class.__recorder.playback!(self, :#{method_name})
210
+ self.send(:#{method_name}, *args, &blk)
211
+ end
212
+ EOM
213
+ end
214
+
215
+ def mark_invoked!(method_name)
216
+ backup_method!(method_name)
217
+ @klass.class_eval(<<-EOM, __FILE__, __LINE__)
218
+ def #{method_name}(*args, &blk)
219
+ method_name = :#{method_name}
220
+ current_instance = self
221
+ invoked_instance = self.class.__recorder.instance_that_received(method_name)
222
+ raise RSpec::Mocks::MockExpectationError, "The message '#{method_name}' was received by \#{self.inspect} but has already been received by \#{invoked_instance}"
223
+ end
224
+ EOM
225
+ end
226
+ end
227
+
228
+ def any_instance
229
+ RSpec::Mocks::space.add(self)
230
+ __recorder
231
+ end
232
+
233
+ def rspec_verify
234
+ __recorder.verify
235
+ super
236
+ ensure
237
+ __recorder.stop_observing!
238
+ @__recorder = nil
239
+ end
240
+
241
+ def __recorder
242
+ @__recorder ||= AnyInstance::Recorder.new(self)
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,23 @@
1
+ if defined?(Psych) && Psych.respond_to?(:dump)
2
+ module Psych
3
+ class << self
4
+ def dump_with_mocks(object, *args)
5
+ return dump_without_mocks(object, *args) unless object.instance_variable_defined?(:@mock_proxy)
6
+
7
+ mp = object.instance_variable_get(:@mock_proxy)
8
+ return dump_without_mocks(object, *args) unless mp.is_a?(::RSpec::Mocks::Proxy)
9
+
10
+ object.send(:remove_instance_variable, :@mock_proxy)
11
+
12
+ begin
13
+ dump_without_mocks(object, *args)
14
+ ensure
15
+ object.instance_variable_set(:@mock_proxy,mp)
16
+ end
17
+ end
18
+
19
+ alias_method :dump_without_mocks, :dump
20
+ alias_method :dump, :dump_with_mocks
21
+ end
22
+ end
23
+ end
@@ -15,3 +15,4 @@ require 'rspec/mocks/errors'
15
15
  require 'rspec/mocks/error_generator'
16
16
  require 'rspec/mocks/space'
17
17
  require 'rspec/mocks/serialization'
18
+ require 'rspec/mocks/any_instance'
@@ -249,22 +249,25 @@ module RSpec
249
249
  end
250
250
 
251
251
  def with(*args, &block)
252
- @return_block = block if block_given?
252
+ @return_block = block if block_given? unless args.empty?
253
253
  @args_expectation = ArgumentExpectation.new(*args, &block)
254
254
  self
255
255
  end
256
256
 
257
- def exactly(n)
257
+ def exactly(n, &block)
258
+ @method_block = block if block
258
259
  set_expected_received_count :exactly, n
259
260
  self
260
261
  end
261
262
 
262
- def at_least(n)
263
+ def at_least(n, &block)
264
+ @method_block = block if block
263
265
  set_expected_received_count :at_least, n
264
266
  self
265
267
  end
266
268
 
267
- def at_most(n)
269
+ def at_most(n, &block)
270
+ @method_block = block if block
268
271
  set_expected_received_count :at_most, n
269
272
  self
270
273
  end
@@ -39,7 +39,7 @@ module RSpec
39
39
  if chain.length > 1
40
40
  if matching_stub = __mock_proxy.__send__(:find_matching_method_stub, chain[0].to_sym)
41
41
  chain.shift
42
- matching_stub.invoke.stub_chain(*chain)
42
+ matching_stub.invoke.stub_chain(*chain, &blk)
43
43
  else
44
44
  next_in_chain = Object.new
45
45
  stub(chain.shift) { next_in_chain }
@@ -41,10 +41,10 @@ module RSpec
41
41
  private
42
42
 
43
43
  def method_missing(sym, *args, &block)
44
+ raise NoMethodError if sym == :to_ary
44
45
  __mock_proxy.record_message_received(sym, *args, &block)
45
46
  begin
46
- return self if __mock_proxy.null_object?
47
- super
47
+ __mock_proxy.null_object? ? self : super
48
48
  rescue NameError
49
49
  __mock_proxy.raise_unexpected_message_error(sym, *args)
50
50
  end
@@ -1,4 +1,5 @@
1
1
  require 'rspec/mocks/extensions/marshal'
2
+ require 'rspec/mocks/extensions/psych' if defined?(::Psych)
2
3
 
3
4
  module RSpec
4
5
  module Mocks
@@ -8,14 +9,15 @@ module RSpec
8
9
  end
9
10
 
10
11
  module YAML
11
- def to_yaml(*a)
12
- return super(*a) unless instance_variable_defined?(:@mock_proxy)
12
+ def to_yaml(options = {})
13
+ return nil if defined?(::Psych) && options.respond_to?(:[]) && options[:nodump]
14
+ return super(options) unless instance_variable_defined?(:@mock_proxy)
13
15
 
14
16
  mp = @mock_proxy
15
17
  remove_instance_variable(:@mock_proxy)
16
18
 
17
19
  begin
18
- super(*a)
20
+ super(options)
19
21
  ensure
20
22
  @mock_proxy = mp
21
23
  end