rspec-mocks 2.0.0.a1

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 (62) hide show
  1. data/.document +5 -0
  2. data/.gitignore +6 -0
  3. data/License.txt +22 -0
  4. data/README.markdown +8 -0
  5. data/Rakefile +50 -0
  6. data/VERSION +1 -0
  7. data/VERSION.yml +5 -0
  8. data/lib/rspec/mocks.rb +201 -0
  9. data/lib/rspec/mocks/argument_expectation.rb +51 -0
  10. data/lib/rspec/mocks/argument_matchers.rb +233 -0
  11. data/lib/rspec/mocks/error_generator.rb +81 -0
  12. data/lib/rspec/mocks/errors.rb +10 -0
  13. data/lib/rspec/mocks/extensions.rb +1 -0
  14. data/lib/rspec/mocks/extensions/object.rb +3 -0
  15. data/lib/rspec/mocks/framework.rb +15 -0
  16. data/lib/rspec/mocks/message_expectation.rb +326 -0
  17. data/lib/rspec/mocks/methods.rb +63 -0
  18. data/lib/rspec/mocks/mock.rb +65 -0
  19. data/lib/rspec/mocks/order_group.rb +29 -0
  20. data/lib/rspec/mocks/proxy.rb +230 -0
  21. data/lib/rspec/mocks/space.rb +28 -0
  22. data/lib/rspec/mocks/spec_methods.rb +39 -0
  23. data/lib/spec/mocks.rb +1 -0
  24. data/spec/rspec/mocks/any_number_of_times_spec.rb +36 -0
  25. data/spec/rspec/mocks/argument_expectation_spec.rb +23 -0
  26. data/spec/rspec/mocks/at_least_spec.rb +97 -0
  27. data/spec/rspec/mocks/at_most_spec.rb +93 -0
  28. data/spec/rspec/mocks/bug_report_10260_spec.rb +8 -0
  29. data/spec/rspec/mocks/bug_report_10263_spec.rb +27 -0
  30. data/spec/rspec/mocks/bug_report_11545_spec.rb +32 -0
  31. data/spec/rspec/mocks/bug_report_15719_spec.rb +29 -0
  32. data/spec/rspec/mocks/bug_report_496_spec.rb +17 -0
  33. data/spec/rspec/mocks/bug_report_600_spec.rb +22 -0
  34. data/spec/rspec/mocks/bug_report_7611_spec.rb +19 -0
  35. data/spec/rspec/mocks/bug_report_7805_spec.rb +22 -0
  36. data/spec/rspec/mocks/bug_report_8165_spec.rb +31 -0
  37. data/spec/rspec/mocks/bug_report_8302_spec.rb +26 -0
  38. data/spec/rspec/mocks/bug_report_830_spec.rb +21 -0
  39. data/spec/rspec/mocks/failing_argument_matchers_spec.rb +95 -0
  40. data/spec/rspec/mocks/hash_including_matcher_spec.rb +90 -0
  41. data/spec/rspec/mocks/hash_not_including_matcher_spec.rb +67 -0
  42. data/spec/rspec/mocks/mock_ordering_spec.rb +94 -0
  43. data/spec/rspec/mocks/mock_space_spec.rb +54 -0
  44. data/spec/rspec/mocks/mock_spec.rb +583 -0
  45. data/spec/rspec/mocks/multiple_return_value_spec.rb +113 -0
  46. data/spec/rspec/mocks/nil_expectation_warning_spec.rb +63 -0
  47. data/spec/rspec/mocks/null_object_mock_spec.rb +54 -0
  48. data/spec/rspec/mocks/once_counts_spec.rb +53 -0
  49. data/spec/rspec/mocks/options_hash_spec.rb +35 -0
  50. data/spec/rspec/mocks/partial_mock_spec.rb +164 -0
  51. data/spec/rspec/mocks/partial_mock_using_mocks_directly_spec.rb +66 -0
  52. data/spec/rspec/mocks/passing_argument_matchers_spec.rb +145 -0
  53. data/spec/rspec/mocks/precise_counts_spec.rb +52 -0
  54. data/spec/rspec/mocks/record_messages_spec.rb +26 -0
  55. data/spec/rspec/mocks/stub_chain_spec.rb +34 -0
  56. data/spec/rspec/mocks/stub_implementation_spec.rb +31 -0
  57. data/spec/rspec/mocks/stub_spec.rb +198 -0
  58. data/spec/rspec/mocks/stubbed_message_expectations_spec.rb +26 -0
  59. data/spec/rspec/mocks/twice_counts_spec.rb +67 -0
  60. data/spec/spec_helper.rb +52 -0
  61. data/spec/support/macros.rb +29 -0
  62. metadata +172 -0
@@ -0,0 +1,65 @@
1
+ module Rspec
2
+ module Mocks
3
+ class Mock
4
+ include Methods
5
+
6
+ # Creates a new mock with a +name+ (that will be used in error messages
7
+ # only) == Options:
8
+ # * <tt>:null_object</tt> - if true, the mock object acts as a forgiving
9
+ # null object allowing any message to be sent to it.
10
+ def initialize(name='mock', stubs_and_options={})
11
+ if name.is_a?(Hash) && stubs_and_options.empty?
12
+ stubs_and_options = name
13
+ build_name_from_options stubs_and_options
14
+ else
15
+ @name = name
16
+ end
17
+ @options = parse_options(stubs_and_options)
18
+ assign_stubs(stubs_and_options)
19
+ end
20
+
21
+ # This allows for comparing the mock to other objects that proxy such as
22
+ # ActiveRecords belongs_to proxy objects. By making the other object run
23
+ # the comparison, we're sure the call gets delegated to the proxy
24
+ # target.
25
+ def ==(other)
26
+ other == __mock_proxy
27
+ end
28
+
29
+ def inspect
30
+ "#<#{self.class}:#{sprintf '0x%x', self.object_id} @name=#{@name.inspect}>"
31
+ end
32
+
33
+ def to_s
34
+ inspect.gsub('<','[').gsub('>',']')
35
+ end
36
+
37
+ private
38
+
39
+ def method_missing(sym, *args, &block)
40
+ __mock_proxy.record_message_received(sym, args, block)
41
+ begin
42
+ return self if __mock_proxy.null_object?
43
+ super(sym, *args, &block)
44
+ rescue NameError
45
+ __mock_proxy.raise_unexpected_message_error sym, *args
46
+ end
47
+ end
48
+
49
+ def parse_options(options)
50
+ options.has_key?(:null_object) ? {:null_object => options.delete(:null_object)} : {}
51
+ end
52
+
53
+ def assign_stubs(stubs)
54
+ stubs.each_pair do |message, response|
55
+ stub!(message).and_return(response)
56
+ end
57
+ end
58
+
59
+ def build_name_from_options(options)
60
+ vals = options.inject([]) {|coll, pair| coll << "#{pair.first}: #{pair.last.inspect}"}
61
+ @name = '{' + vals.join(', ') + '}'
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,29 @@
1
+ module Rspec
2
+ module Mocks
3
+ class OrderGroup
4
+ def initialize error_generator
5
+ @error_generator = error_generator
6
+ @ordering = Array.new
7
+ end
8
+
9
+ def register(expectation)
10
+ @ordering << expectation
11
+ end
12
+
13
+ def ready_for?(expectation)
14
+ return @ordering.first == expectation
15
+ end
16
+
17
+ def consume
18
+ @ordering.shift
19
+ end
20
+
21
+ def handle_order_constraint expectation
22
+ return unless @ordering.include? expectation
23
+ return consume if ready_for?(expectation)
24
+ @error_generator.raise_out_of_order_error expectation.sym
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,230 @@
1
+ module Rspec
2
+ module Mocks
3
+ class Proxy
4
+ DEFAULT_OPTIONS = {
5
+ :null_object => false,
6
+ }
7
+
8
+ @@warn_about_expectations_on_nil = true
9
+
10
+ def self.allow_message_expectations_on_nil
11
+ @@warn_about_expectations_on_nil = false
12
+
13
+ # ensure nil.rspec_verify is called even if an expectation is not set in the example
14
+ # otherwise the allowance would effect subsequent examples
15
+ $rspec_mocks.add(nil) unless $rspec_mocks.nil?
16
+ end
17
+
18
+ def initialize(target, name=nil, options={})
19
+ @target = target
20
+ @name = name
21
+ @error_generator = ErrorGenerator.new target, name
22
+ @expectation_ordering = OrderGroup.new @error_generator
23
+ @expectations = []
24
+ @messages_received = []
25
+ @stubs = []
26
+ @proxied_methods = []
27
+ @options = options ? DEFAULT_OPTIONS.dup.merge(options) : DEFAULT_OPTIONS
28
+ @already_proxied_respond_to = false
29
+ end
30
+
31
+ def null_object?
32
+ @options[:null_object]
33
+ end
34
+
35
+ def as_null_object
36
+ @options[:null_object] = true
37
+ @target
38
+ end
39
+
40
+ def add_message_expectation(expected_from, sym, opts={}, &block)
41
+ __add sym
42
+ warn_if_nil_class sym
43
+ if existing_stub = @stubs.detect {|s| s.sym == sym }
44
+ expectation = existing_stub.build_child(expected_from, block_given?? block : nil, 1, opts)
45
+ else
46
+ expectation = MessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil, 1, opts)
47
+ end
48
+ @expectations << expectation
49
+ @expectations.last
50
+ end
51
+
52
+ def add_negative_message_expectation(expected_from, sym, &block)
53
+ __add sym
54
+ warn_if_nil_class sym
55
+ @expectations << NegativeMessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil)
56
+ @expectations.last
57
+ end
58
+
59
+ def add_stub(expected_from, sym, opts={}, &implementation)
60
+ __add sym
61
+ @stubs.unshift MessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, nil, :any, opts, &implementation)
62
+ @stubs.first
63
+ end
64
+
65
+ def verify #:nodoc:
66
+ verify_expectations
67
+ ensure
68
+ reset
69
+ end
70
+
71
+ def reset
72
+ clear_expectations
73
+ clear_stubs
74
+ reset_proxied_methods
75
+ clear_proxied_methods
76
+ reset_nil_expectations_warning
77
+ end
78
+
79
+ def received_message?(sym, *args, &block)
80
+ @messages_received.any? {|array| array == [sym, args, block]}
81
+ end
82
+
83
+ def has_negative_expectation?(sym)
84
+ @expectations.detect {|expectation| expectation.negative_expectation_for?(sym)}
85
+ end
86
+
87
+ def record_message_received(sym, args, block)
88
+ @messages_received << [sym, args, block]
89
+ end
90
+
91
+ def message_received(sym, *args, &block)
92
+ expectation = find_matching_expectation(sym, *args)
93
+ stub = find_matching_method_stub(sym, *args)
94
+
95
+ if (stub && expectation && expectation.called_max_times?) || (stub && !expectation)
96
+ if expectation = find_almost_matching_expectation(sym, *args)
97
+ expectation.advise(args, block) unless expectation.expected_messages_received?
98
+ end
99
+ stub.invoke(args, block)
100
+ elsif expectation
101
+ expectation.invoke(args, block)
102
+ elsif expectation = find_almost_matching_expectation(sym, *args)
103
+ expectation.advise(args, block) if null_object? unless expectation.expected_messages_received?
104
+ raise_unexpected_message_args_error(expectation, *args) unless (has_negative_expectation?(sym) or null_object?)
105
+ else
106
+ @target.__send__ :method_missing, sym, *args, &block
107
+ end
108
+ end
109
+
110
+ def raise_unexpected_message_args_error(expectation, *args)
111
+ @error_generator.raise_unexpected_message_args_error expectation, *args
112
+ end
113
+
114
+ def raise_unexpected_message_error(sym, *args)
115
+ @error_generator.raise_unexpected_message_error sym, *args
116
+ end
117
+
118
+ private
119
+
120
+ def __add(sym)
121
+ $rspec_mocks.add(@target) unless $rspec_mocks.nil?
122
+ define_expected_method(sym)
123
+ end
124
+
125
+ def warn_if_nil_class(sym)
126
+ if proxy_for_nil_class? & @@warn_about_expectations_on_nil
127
+ Kernel.warn("An expectation of :#{sym} was set on nil. Called from #{caller[2]}. Use allow_message_expectations_on_nil to disable warnings.")
128
+ end
129
+ end
130
+
131
+ def define_expected_method(sym)
132
+ unless @proxied_methods.include?(sym)
133
+ visibility_string = "#{visibility(sym)} :#{sym}"
134
+ if target_responds_to?(sym)
135
+ munged_sym = munge(sym)
136
+ target_metaclass.instance_eval do
137
+ alias_method munged_sym, sym if method_defined?(sym)
138
+ end
139
+ @proxied_methods << sym
140
+ end
141
+ target_metaclass.class_eval(<<-EOF, __FILE__, __LINE__)
142
+ def #{sym}(*args, &block)
143
+ __mock_proxy.message_received :#{sym}, *args, &block
144
+ end
145
+ #{visibility_string}
146
+ EOF
147
+ end
148
+ end
149
+
150
+ def target_responds_to?(sym)
151
+ return @target.__send__(munge(:respond_to?),sym) if @already_proxied_respond_to
152
+ return @already_proxied_respond_to = true if sym == :respond_to?
153
+ return @target.respond_to?(sym, true)
154
+ end
155
+
156
+ def visibility(sym)
157
+ if Mock === @target
158
+ 'public'
159
+ elsif target_metaclass.private_method_defined?(sym)
160
+ 'private'
161
+ elsif target_metaclass.protected_method_defined?(sym)
162
+ 'protected'
163
+ else
164
+ 'public'
165
+ end
166
+ end
167
+
168
+ def munge(sym)
169
+ "proxied_by_rspec__#{sym}"
170
+ end
171
+
172
+ def clear_expectations
173
+ @expectations.clear
174
+ end
175
+
176
+ def clear_stubs
177
+ @stubs.clear
178
+ end
179
+
180
+ def clear_proxied_methods
181
+ @proxied_methods.clear
182
+ end
183
+
184
+ def target_metaclass
185
+ class << @target; self; end
186
+ end
187
+
188
+ def verify_expectations
189
+ @expectations.each do |expectation|
190
+ expectation.verify_messages_received
191
+ end
192
+ end
193
+
194
+ def reset_proxied_methods
195
+ @proxied_methods.each do |sym|
196
+ munged_sym = munge(sym)
197
+ target_metaclass.instance_eval do
198
+ remove_method sym
199
+ if method_defined?(munged_sym)
200
+ alias_method sym, munged_sym
201
+ remove_method munged_sym
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ def proxy_for_nil_class?
208
+ @target.nil?
209
+ end
210
+
211
+ def reset_nil_expectations_warning
212
+ @@warn_about_expectations_on_nil = true if proxy_for_nil_class?
213
+ end
214
+
215
+ def find_matching_expectation(sym, *args)
216
+ @expectations.find {|expectation| expectation.matches(sym, args) && !expectation.called_max_times?} ||
217
+ @expectations.find {|expectation| expectation.matches(sym, args)}
218
+ end
219
+
220
+ def find_almost_matching_expectation(sym, *args)
221
+ @expectations.find {|expectation| expectation.matches_name_but_not_args(sym, args)}
222
+ end
223
+
224
+ def find_matching_method_stub(sym, *args)
225
+ @stubs.find {|stub| stub.matches(sym, args)}
226
+ end
227
+
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,28 @@
1
+ module Rspec
2
+ module Mocks
3
+ class Space
4
+ def add(obj)
5
+ mocks << obj unless mocks.detect {|m| m.equal? obj}
6
+ end
7
+
8
+ def verify_all
9
+ mocks.each do |mock|
10
+ mock.rspec_verify
11
+ end
12
+ end
13
+
14
+ def reset_all
15
+ mocks.each do |mock|
16
+ mock.rspec_reset
17
+ end
18
+ mocks.clear
19
+ end
20
+
21
+ private
22
+
23
+ def mocks
24
+ @mocks ||= []
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,39 @@
1
+ module Rspec
2
+ module Mocks
3
+ module ExampleMethods
4
+ include Rspec::Mocks::ArgumentMatchers
5
+
6
+ # Shortcut for creating an instance of Rspec::Mocks::Mock.
7
+ #
8
+ # +name+ is used for failure reporting, so you should use the
9
+ # role that the mock is playing in the example.
10
+ #
11
+ # +stubs_and_options+ lets you assign options and stub values
12
+ # at the same time. The only option available is :null_object.
13
+ # Anything else is treated as a stub value.
14
+ #
15
+ # == Examples
16
+ #
17
+ # stub_thing = mock("thing", :a => "A")
18
+ # stub_thing.a == "A" => true
19
+ #
20
+ # stub_person = stub("thing", :name => "Joe", :email => "joe@domain.com")
21
+ # stub_person.name => "Joe"
22
+ # stub_person.email => "joe@domain.com"
23
+ def mock(*args)
24
+ Rspec::Mocks::Mock.new(*args)
25
+ end
26
+
27
+ alias :stub :mock
28
+
29
+ # Disables warning messages about expectations being set on nil.
30
+ #
31
+ # By default warning messages are issued when expectations are set on nil. This is to
32
+ # prevent false-positives and to catch potential bugs early on.
33
+ def allow_message_expectations_on_nil
34
+ Proxy.allow_message_expectations_on_nil
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1 @@
1
+ require 'rspec/mocks'
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
+
3
+ module Rspec
4
+ module Mocks
5
+
6
+ describe "AnyNumberOfTimes" do
7
+ before(:each) do
8
+ @mock = Mock.new("test mock")
9
+ end
10
+
11
+ it "should pass if any number of times method is called many times" do
12
+ @mock.should_receive(:random_call).any_number_of_times
13
+ (1..10).each do
14
+ @mock.random_call
15
+ end
16
+ end
17
+
18
+ it "should pass if any number of times method is called once" do
19
+ @mock.should_receive(:random_call).any_number_of_times
20
+ @mock.random_call
21
+ end
22
+
23
+ it "should pass if any number of times method is not called" do
24
+ @mock.should_receive(:random_call).any_number_of_times
25
+ end
26
+
27
+ it "should return the mocked value when called after a similar stub" do
28
+ @mock.stub!(:message).and_return :stub_value
29
+ @mock.should_receive(:message).any_number_of_times.and_return(:mock_value)
30
+ @mock.message.should == :mock_value
31
+ @mock.message.should == :mock_value
32
+ end
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
+
3
+ module Rspec
4
+ module Mocks
5
+ describe ArgumentExpectation do
6
+ it "should consider an object that responds to #matches? and #description to be a matcher" do
7
+ argument_expecatation = Rspec::Mocks::ArgumentExpectation.new([])
8
+ obj = mock("matcher")
9
+ obj.should_receive(:respond_to?).with(:matches?).and_return(true)
10
+ obj.should_receive(:respond_to?).with(:description).and_return(true)
11
+ argument_expecatation.is_matcher?(obj).should be_true
12
+ end
13
+
14
+ it "should NOT consider an object that only responds to #matches? to be a matcher" do
15
+ argument_expecatation = Rspec::Mocks::ArgumentExpectation.new([])
16
+ obj = mock("matcher")
17
+ obj.should_receive(:respond_to?).with(:matches?).and_return(true)
18
+ obj.should_receive(:respond_to?).with(:description).and_return(false)
19
+ argument_expecatation.is_matcher?(obj).should be_false
20
+ end
21
+ end
22
+ end
23
+ end