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.
- data/.document +5 -0
- data/.gitignore +6 -0
- data/License.txt +22 -0
- data/README.markdown +8 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/VERSION.yml +5 -0
- data/lib/rspec/mocks.rb +201 -0
- data/lib/rspec/mocks/argument_expectation.rb +51 -0
- data/lib/rspec/mocks/argument_matchers.rb +233 -0
- data/lib/rspec/mocks/error_generator.rb +81 -0
- data/lib/rspec/mocks/errors.rb +10 -0
- data/lib/rspec/mocks/extensions.rb +1 -0
- data/lib/rspec/mocks/extensions/object.rb +3 -0
- data/lib/rspec/mocks/framework.rb +15 -0
- data/lib/rspec/mocks/message_expectation.rb +326 -0
- data/lib/rspec/mocks/methods.rb +63 -0
- data/lib/rspec/mocks/mock.rb +65 -0
- data/lib/rspec/mocks/order_group.rb +29 -0
- data/lib/rspec/mocks/proxy.rb +230 -0
- data/lib/rspec/mocks/space.rb +28 -0
- data/lib/rspec/mocks/spec_methods.rb +39 -0
- data/lib/spec/mocks.rb +1 -0
- data/spec/rspec/mocks/any_number_of_times_spec.rb +36 -0
- data/spec/rspec/mocks/argument_expectation_spec.rb +23 -0
- data/spec/rspec/mocks/at_least_spec.rb +97 -0
- data/spec/rspec/mocks/at_most_spec.rb +93 -0
- data/spec/rspec/mocks/bug_report_10260_spec.rb +8 -0
- data/spec/rspec/mocks/bug_report_10263_spec.rb +27 -0
- data/spec/rspec/mocks/bug_report_11545_spec.rb +32 -0
- data/spec/rspec/mocks/bug_report_15719_spec.rb +29 -0
- data/spec/rspec/mocks/bug_report_496_spec.rb +17 -0
- data/spec/rspec/mocks/bug_report_600_spec.rb +22 -0
- data/spec/rspec/mocks/bug_report_7611_spec.rb +19 -0
- data/spec/rspec/mocks/bug_report_7805_spec.rb +22 -0
- data/spec/rspec/mocks/bug_report_8165_spec.rb +31 -0
- data/spec/rspec/mocks/bug_report_8302_spec.rb +26 -0
- data/spec/rspec/mocks/bug_report_830_spec.rb +21 -0
- data/spec/rspec/mocks/failing_argument_matchers_spec.rb +95 -0
- data/spec/rspec/mocks/hash_including_matcher_spec.rb +90 -0
- data/spec/rspec/mocks/hash_not_including_matcher_spec.rb +67 -0
- data/spec/rspec/mocks/mock_ordering_spec.rb +94 -0
- data/spec/rspec/mocks/mock_space_spec.rb +54 -0
- data/spec/rspec/mocks/mock_spec.rb +583 -0
- data/spec/rspec/mocks/multiple_return_value_spec.rb +113 -0
- data/spec/rspec/mocks/nil_expectation_warning_spec.rb +63 -0
- data/spec/rspec/mocks/null_object_mock_spec.rb +54 -0
- data/spec/rspec/mocks/once_counts_spec.rb +53 -0
- data/spec/rspec/mocks/options_hash_spec.rb +35 -0
- data/spec/rspec/mocks/partial_mock_spec.rb +164 -0
- data/spec/rspec/mocks/partial_mock_using_mocks_directly_spec.rb +66 -0
- data/spec/rspec/mocks/passing_argument_matchers_spec.rb +145 -0
- data/spec/rspec/mocks/precise_counts_spec.rb +52 -0
- data/spec/rspec/mocks/record_messages_spec.rb +26 -0
- data/spec/rspec/mocks/stub_chain_spec.rb +34 -0
- data/spec/rspec/mocks/stub_implementation_spec.rb +31 -0
- data/spec/rspec/mocks/stub_spec.rb +198 -0
- data/spec/rspec/mocks/stubbed_message_expectations_spec.rb +26 -0
- data/spec/rspec/mocks/twice_counts_spec.rb +67 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/support/macros.rb +29 -0
- 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
|
data/lib/spec/mocks.rb
ADDED
@@ -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
|