rspec-mocks 2.5.0 → 2.6.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 (53) hide show
  1. data/.travis.yml +7 -0
  2. data/Gemfile +14 -13
  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/Upgrade.md +10 -2
  9. data/features/message_expectations/any_instance.feature +19 -0
  10. data/features/message_expectations/block_local_expectations.feature.pending +2 -2
  11. data/features/message_expectations/expect_message.feature +3 -3
  12. data/features/message_expectations/warn_when_expectation_is_set_on_nil.feature +3 -3
  13. data/features/method_stubs/README.md +32 -24
  14. data/features/method_stubs/any_instance.feature +23 -0
  15. data/features/method_stubs/as_null_object.feature +36 -0
  16. data/features/method_stubs/simple_return_value.feature +2 -2
  17. data/features/method_stubs/stub_chain.feature +13 -6
  18. data/features/method_stubs/stub_implementation.feature +1 -1
  19. data/features/method_stubs/to_ary.feature +45 -0
  20. data/features/outside_rspec/configuration.feature +3 -3
  21. data/features/outside_rspec/standalone.feature +2 -2
  22. data/features/step_definitions/additional_cli_steps.rb +4 -0
  23. data/features/support/env.rb +5 -1
  24. data/lib/rspec/mocks/any_instance.rb +256 -0
  25. data/lib/rspec/mocks/argument_expectation.rb +9 -8
  26. data/lib/rspec/mocks/extensions/marshal.rb +0 -4
  27. data/lib/rspec/mocks/extensions/psych.rb +23 -0
  28. data/lib/rspec/mocks/framework.rb +1 -0
  29. data/lib/rspec/mocks/message_expectation.rb +7 -4
  30. data/lib/rspec/mocks/methods.rb +1 -1
  31. data/lib/rspec/mocks/mock.rb +2 -2
  32. data/lib/rspec/mocks/serialization.rb +5 -3
  33. data/lib/rspec/mocks/space.rb +1 -1
  34. data/lib/rspec/mocks/version.rb +1 -1
  35. data/lib/rspec/mocks.rb +2 -1
  36. data/spec/rspec/mocks/and_yield_spec.rb +3 -3
  37. data/spec/rspec/mocks/any_instance_spec.rb +590 -0
  38. data/spec/rspec/mocks/any_number_of_times_spec.rb +22 -28
  39. data/spec/rspec/mocks/at_least_spec.rb +6 -0
  40. data/spec/rspec/mocks/at_most_spec.rb +6 -0
  41. data/spec/rspec/mocks/bug_report_10263_spec.rb +12 -14
  42. data/spec/rspec/mocks/bug_report_7611_spec.rb +2 -4
  43. data/spec/rspec/mocks/failing_argument_matchers_spec.rb +45 -46
  44. data/spec/rspec/mocks/nil_expectation_warning_spec.rb +1 -2
  45. data/spec/rspec/mocks/passing_argument_matchers_spec.rb +119 -122
  46. data/spec/rspec/mocks/precise_counts_spec.rb +6 -0
  47. data/spec/rspec/mocks/serialization_spec.rb +21 -3
  48. data/spec/rspec/mocks/stub_chain_spec.rb +72 -37
  49. data/spec/rspec/mocks/stub_spec.rb +64 -61
  50. data/spec/rspec/mocks/to_ary_spec.rb +31 -0
  51. metadata +24 -11
  52. data/spec/rspec/mocks/bug_report_7805_spec.rb +0 -22
  53. data/spec/rspec/mocks/bug_report_8302_spec.rb +0 -26
@@ -0,0 +1,256 @@
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_all_observation!
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 stop_observing!(method_name)
205
+ restore_method!(method_name)
206
+ @observed_methods.delete(method_name)
207
+ end
208
+
209
+ def already_observing?(method_name)
210
+ @observed_methods.include?(method_name)
211
+ end
212
+
213
+ def observe!(method_name)
214
+ stop_observing!(method_name) if already_observing?(method_name)
215
+ @observed_methods << method_name
216
+ backup_method!(method_name)
217
+ @klass.class_eval(<<-EOM, __FILE__, __LINE__)
218
+ def #{method_name}(*args, &blk)
219
+ self.class.__recorder.playback!(self, :#{method_name})
220
+ self.send(:#{method_name}, *args, &blk)
221
+ end
222
+ EOM
223
+ end
224
+
225
+ def mark_invoked!(method_name)
226
+ backup_method!(method_name)
227
+ @klass.class_eval(<<-EOM, __FILE__, __LINE__)
228
+ def #{method_name}(*args, &blk)
229
+ method_name = :#{method_name}
230
+ current_instance = self
231
+ invoked_instance = self.class.__recorder.instance_that_received(method_name)
232
+ raise RSpec::Mocks::MockExpectationError, "The message '#{method_name}' was received by \#{self.inspect} but has already been received by \#{invoked_instance}"
233
+ end
234
+ EOM
235
+ end
236
+ end
237
+
238
+ def any_instance
239
+ RSpec::Mocks::space.add(self)
240
+ __recorder
241
+ end
242
+
243
+ def rspec_verify
244
+ __recorder.verify
245
+ super
246
+ ensure
247
+ __recorder.stop_all_observation!
248
+ @__recorder = nil
249
+ end
250
+
251
+ def __recorder
252
+ @__recorder ||= AnyInstance::Recorder.new(self)
253
+ end
254
+ end
255
+ end
256
+ end
@@ -5,7 +5,7 @@ module RSpec
5
5
 
6
6
  def initialize(*args, &block)
7
7
  @args = args
8
- @matchers_block = args.empty? ? block : nil
8
+ @block = args.empty? ? block : nil
9
9
  @match_any_args = false
10
10
  @matchers = nil
11
11
 
@@ -26,19 +26,20 @@ module RSpec
26
26
  end
27
27
 
28
28
  def is_matcher?(obj)
29
- !is_stub_as_null_object?(obj) & obj.respond_to?(:matches?) & obj.respond_to?(:description)
29
+ !null_object?(obj) & obj.respond_to?(:matches?) & obj.respond_to?(:description)
30
30
  end
31
31
 
32
- def is_stub_as_null_object?(obj)
33
- obj.respond_to?(:__rspec_double_acting_as_null_object?) && obj.__rspec_double_acting_as_null_object?
32
+ def args_match?(*args)
33
+ match_any_args? || block_passes?(*args) || matchers_match?(*args)
34
34
  end
35
35
 
36
- def args_match?(*args)
37
- match_any_args? || matchers_block_matches?(*args) || matchers_match?(*args)
36
+ private
37
+ def null_object?(obj)
38
+ obj.respond_to?(:__rspec_double_acting_as_null_object?) && obj.__rspec_double_acting_as_null_object?
38
39
  end
39
40
 
40
- def matchers_block_matches?(*args)
41
- @matchers_block ? @matchers_block.call(*args) : nil
41
+ def block_passes?(*args)
42
+ @block.call(*args) if @block
42
43
  end
43
44
 
44
45
  def matchers_match?(*args)
@@ -1,7 +1,5 @@
1
1
  module Marshal
2
-
3
2
  class << self
4
-
5
3
  def dump_with_mocks(*args)
6
4
  object = args.shift
7
5
  return dump_without_mocks(*args.unshift(object)) unless object.instance_variable_defined?(:@mock_proxy)
@@ -21,7 +19,5 @@ module Marshal
21
19
  alias_method :dump_without_mocks, :dump
22
20
  undef_method :dump
23
21
  alias_method :dump, :dump_with_mocks
24
-
25
22
  end
26
-
27
23
  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
@@ -10,7 +10,7 @@ module RSpec
10
10
  mock.rspec_verify
11
11
  end
12
12
  end
13
-
13
+
14
14
  def reset_all
15
15
  mocks.each do |mock|
16
16
  mock.rspec_reset
@@ -1,7 +1,7 @@
1
1
  module RSpec # :nodoc:
2
2
  module Mocks # :nodoc:
3
3
  module Version # :nodoc:
4
- STRING = '2.5.0'
4
+ STRING = '2.6.0'
5
5
  end
6
6
  end
7
7
  end
data/lib/rspec/mocks.rb CHANGED
@@ -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
@@ -1,9 +1,9 @@
1
1
  require 'spec_helper'
2
- def obj
3
- @obj ||= mock
4
- end
5
2
 
6
3
  describe RSpec::Mocks::Mock do
4
+
5
+ let(:obj) { double }
6
+
7
7
  describe "#and_yield" do
8
8
  context "with eval context as block argument" do
9
9