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