Class: Minitest::Mock
Overview
A simple and clean mock object framework.
All mock objects are an instance of Mock
Instance Method Summary collapse
-
#__call(name, data) ⇒ Object
:nodoc:.
- #__respond_to? ⇒ Object
-
#expect(name, retval, args = [], &blk) ⇒ Object
Expect that method
name
is called, optionally withargs
or ablk
, and returnsretval
. -
#initialize(delegator = nil) ⇒ Mock
constructor
:nodoc:.
-
#method_missing(sym, *args, &block) ⇒ Object
:nodoc:.
-
#respond_to?(sym, include_private = false) ⇒ Boolean
:nodoc:.
-
#verify ⇒ Object
Verify that all methods were called as expected.
Constructor Details
#initialize(delegator = nil) ⇒ Mock
:nodoc:
40 41 42 43 44 |
# File 'lib/minitest/mock.rb', line 40 def initialize delegator = nil # :nodoc: @delegator = delegator @expected_calls = Hash.new { |calls, name| calls[name] = [] } @actual_calls = Hash.new { |calls, name| calls[name] = [] } end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(sym, *args, &block) ⇒ Object
:nodoc:
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 |
# File 'lib/minitest/mock.rb', line 118 def method_missing sym, *args, &block # :nodoc: unless @expected_calls.key?(sym) then if @delegator && @delegator.respond_to?(sym) return @delegator.public_send(sym, *args, &block) else raise NoMethodError, "unmocked method %p, expected one of %p" % [sym, @expected_calls.keys.sort_by(&:to_s)] end 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, val_block = expected_call.values_at(:args, :retval, :block) if val_block then # keep "verify" happy @actual_calls[sym] << expected_call raise MockExpectationError, "mocked method %p failed block w/ %p" % [sym, args] unless val_block.call(*args, &block) return retval end if expected_args.size != args.size then raise ArgumentError, "mocked method %p expects %d arguments, got %d" % [sym, expected_args.size, args.size] end zipped_args = expected_args.zip(args) fully_matched = zipped_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 => zipped_args.map! { |mod, a| mod === a ? mod : a }, } retval end |
Instance Method Details
#__call(name, data) ⇒ Object
:nodoc:
94 95 96 97 98 99 100 101 |
# File 'lib/minitest/mock.rb', line 94 def __call name, data # :nodoc: case data when Hash then "#{name}(#{data[:args].inspect[1..-2]}) => #{data[:retval].inspect}" else data.map { |d| __call name, d }.join ", " end end |
#__respond_to? ⇒ Object
11 |
# File 'lib/minitest/mock.rb', line 11 alias :__respond_to? :respond_to? |
#expect(name, retval, args = [], &blk) ⇒ Object
Expect that method name
is called, optionally with args
or a blk
, 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
@mock.expect(:do_something_else, true) do |a1, a2|
a1 == "buggs" && a2 == :bunny
end
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") # => raises MockExpectationError
If a method will be called multiple times, specify a new expect for each one. They will be used in the order you define them.
@mock.expect(:ordinal_increment, 'first')
@mock.expect(:ordinal_increment, 'second')
@mock.ordinal_increment # => 'first'
@mock.ordinal_increment # => 'second'
@mock.ordinal_increment # => raises MockExpectationError "No more expects available for :ordinal_increment"
81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/minitest/mock.rb', line 81 def expect name, retval, args = [], &blk name = name.to_sym if block_given? raise ArgumentError, "args ignored when block given" unless args.empty? @expected_calls[name] << { :retval => retval, :block => blk } else raise ArgumentError, "args must be an array" unless Array === args @expected_calls[name] << { :retval => retval, :args => args } end self end |
#respond_to?(sym, include_private = false) ⇒ Boolean
:nodoc:
172 173 174 175 176 |
# File 'lib/minitest/mock.rb', line 172 def respond_to? sym, include_private = false # :nodoc: return true if @expected_calls.key? sym.to_sym return true if @delegator && @delegator.respond_to?(sym, include_private) __respond_to?(sym, include_private) end |
#verify ⇒ Object
Verify that all methods were called as expected. Raises MockExpectationError
if the mock object was not called as expected.
108 109 110 111 112 113 114 115 116 |
# File 'lib/minitest/mock.rb', line 108 def verify @expected_calls.each do |name, expected| actual = @actual_calls.fetch(name, nil) raise MockExpectationError, "expected #{__call name, expected[0]}" unless actual raise MockExpectationError, "expected #{__call name, expected[actual.size]}, got [#{__call name, actual}]" if actual.size < expected.size end true end |