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
|
# encoding: utf-8
######################################################################
# This file is imported from the minitest project.
# DO NOT make modifications in this repo. They _will_ be reverted!
# File a patch instead and assign it to Ryan Davis.
######################################################################
class MockExpectationError < StandardError # :nodoc:
end # omg... worst bug ever. rdoc doesn't allow 1-liners
##
# A simple and clean mock object framework.
module MiniTest
##
# All mock objects are an instance of Mock
class Mock
alias :__respond_to? :respond_to?
skip_methods = %w(object_id respond_to_missing? inspect === to_s)
instance_methods.each do |m|
undef_method m unless skip_methods.include?(m.to_s) || m =~ /^__/
end
def initialize # :nodoc:
@expected_calls = Hash.new { |calls, name| calls[name] = [] }
@actual_calls = Hash.new { |calls, name| calls[name] = [] }
end
##
# Expect that method +name+ is called, optionally with +args+, and returns
# +retval+.
#
# @mock.expect(:meaning_of_life, 42)
# @mock.meaning_of_life # => 42
#
# @mock.expect(:do_something_with, true, [some_obj, true])
# @mock.do_something_with(some_obj, true) # => true
#
# +args+ is compared to the expected args using case equality (ie, the
# '===' operator), allowing for less specific expectations.
#
# @mock.expect(:uses_any_string, true, [String])
# @mock.uses_any_string("foo") # => true
# @mock.verify # => true
#
# @mock.expect(:uses_one_string, true, ["foo"]
# @mock.uses_one_string("bar") # => true
# @mock.verify # => raises MockExpectationError
def expect(name, retval, args=[])
raise ArgumentError, "args must be an array" unless Array === args
@expected_calls[name] << { :retval => retval, :args => args }
self
end
def call name, data
case data
when Hash then
"#{name}(#{data[:args].inspect[1..-2]}) => #{data[:retval].inspect}"
else
data.map { |d| call name, d }.join ", "
end
end
##
# Verify that all methods were called as expected. Raises
# +MockExpectationError+ if the mock object was not called as
# expected.
def verify
@expected_calls.each do |name, calls|
calls.each do |expected|
msg1 = "expected #{call name, expected}"
msg2 = "#{msg1}, got [#{call name, @actual_calls[name]}]"
raise MockExpectationError, msg2 if
@actual_calls.has_key? name and
not @actual_calls[name].include?(expected)
raise MockExpectationError, msg1 unless
@actual_calls.has_key? name and @actual_calls[name].include?(expected)
end
end
true
end
def method_missing(sym, *args) # :nodoc:
unless @expected_calls.has_key?(sym) then
raise NoMethodError, "unmocked method %p, expected one of %p" %
[sym, @expected_calls.keys.sort_by(&:to_s)]
end
index = @actual_calls[sym].length
expected_call = @expected_calls[sym][index]
unless expected_call then
raise MockExpectationError, "No more expects available for %p: %p" %
[sym, args]
end
expected_args, retval = expected_call[:args], expected_call[:retval]
if expected_args.size != args.size then
raise ArgumentError, "mocked method %p expects %d arguments, got %d" %
[sym, expected_args.size, args.size]
end
fully_matched = expected_args.zip(args).all? { |mod, a|
mod === a or mod == a
}
unless fully_matched then
raise MockExpectationError, "mocked method %p called with unexpected arguments %p" %
[sym, args]
end
@actual_calls[sym] << {
:retval => retval,
:args => expected_args.zip(args).map { |mod, a| mod === a ? mod : a }
}
retval
end
def respond_to?(sym) # :nodoc:
return true if @expected_calls.has_key?(sym.to_sym)
return __respond_to?(sym)
end
end
end
|