rspec-mocks 2.5.0 → 2.6.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
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