1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
require 'mspec/expectations/expectations'
require 'mspec/helpers/warning'
module Mock
def self.reset
@mocks = @stubs = @objects = nil
end
def self.objects
@objects ||= {}
end
def self.mocks
@mocks ||= Hash.new { |h,k| h[k] = [] }
end
def self.stubs
@stubs ||= Hash.new { |h,k| h[k] = [] }
end
def self.replaced_name(key)
:"__mspec_#{key.last}__"
end
def self.replaced_key(obj, sym)
[obj.__id__, sym]
end
def self.replaced?(key)
mocks.include?(key) or stubs.include?(key)
end
def self.clear_replaced(key)
mocks.delete key
stubs.delete key
end
def self.mock_respond_to?(obj, sym, include_private = false)
key = replaced_key(obj, :respond_to?)
if replaced? key
name = replaced_name(key)
obj.__send__ name, sym, include_private
else
obj.respond_to? sym, include_private
end
end
def self.install_method(obj, sym, type = nil)
meta = obj.singleton_class
key = replaced_key obj, sym
sym = sym.to_sym
if type == :stub and mocks.key?(key)
# Defining a stub and there is already a mock, ignore the stub
return
end
if (sym == :respond_to? or mock_respond_to?(obj, sym, true)) and !replaced?(key)
meta.__send__ :alias_method, replaced_name(key), sym
end
suppress_warning {
meta.class_eval {
define_method(sym) do |*args, &block|
Mock.verify_call self, sym, *args, &block
end
}
}
proxy = MockProxy.new type
if proxy.mock?
MSpec.expectation
MSpec.actions :expectation, MSpec.current.state
end
if proxy.mock? and stubs.key?(key)
# Defining a mock and there is already a stub, remove the stub
stubs.delete key
end
if proxy.stub?
stubs[key].unshift proxy
else
mocks[key] << proxy
end
objects[key] = obj
proxy
end
def self.name_or_inspect(obj)
obj.instance_variable_get(:@name) || obj.inspect
end
def self.inspect_args(args)
"(#{Array(args).map(&:inspect).join(', ')})"
end
def self.verify_count
mocks.each do |key, proxies|
obj = objects[key]
proxies.each do |proxy|
qualifier, count = proxy.count
pass = case qualifier
when :at_least
proxy.calls >= count
when :at_most
proxy.calls <= count
when :exactly
proxy.calls == count
when :any_number_of_times
true
else
false
end
unless pass
SpecExpectation.fail_with(
"Mock '#{name_or_inspect obj}' expected to receive #{key.last}#{inspect_args proxy.arguments} " + \
"#{qualifier.to_s.sub('_', ' ')} #{count} times",
"but received it #{proxy.calls} times")
end
end
end
end
def self.verify_call(obj, sym, *args, &block)
compare = *args
compare = compare.first if compare.length <= 1
key = replaced_key obj, sym
[mocks, stubs].each do |proxies|
proxies.fetch(key, []).each do |proxy|
pass = case proxy.arguments
when :any_args
true
when :no_args
compare.nil?
else
proxy.arguments == compare
end
if proxy.yielding?
if block
proxy.yielding.each do |args_to_yield|
if block.arity == -1 || block.arity == args_to_yield.size
block.call(*args_to_yield)
else
SpecExpectation.fail_with(
"Mock '#{name_or_inspect obj}' asked to yield " + \
"|#{proxy.yielding.join(', ')}| on #{sym}\n",
"but a block with arity #{block.arity} was passed")
end
end
else
SpecExpectation.fail_with(
"Mock '#{name_or_inspect obj}' asked to yield " + \
"|[#{proxy.yielding.join('], [')}]| on #{sym}\n",
"but no block was passed")
end
end
if pass
proxy.called
if proxy.raising?
raise proxy.raising
else
return proxy.returning
end
end
end
end
if sym.to_sym == :respond_to?
mock_respond_to? obj, *args
else
SpecExpectation.fail_with("Mock '#{name_or_inspect obj}': method #{sym}\n",
"called with unexpected arguments #{inspect_args args}")
end
end
def self.cleanup
objects.each do |key, obj|
if obj.kind_of? MockIntObject
clear_replaced key
next
end
replaced = replaced_name(key)
sym = key.last
meta = obj.singleton_class
if mock_respond_to? obj, replaced, true
suppress_warning do
meta.__send__ :alias_method, sym, replaced
end
meta.__send__ :remove_method, replaced
else
meta.__send__ :remove_method, sym
end
clear_replaced key
end
ensure
reset
end
end
|