Class: Minitest::Mock
Overview
A simple and clean mock object framework.
All mock objects are an instance of Mock
Constant Summary collapse
- @@KW_WARNED =
:nodoc:
false
Instance Method Summary collapse
-
#__call(name, data) ⇒ Object
:nodoc:.
- #__respond_to? ⇒ Object
-
#expect(name, retval, args = [], **kwargs, &blk) ⇒ Object
Expect that method
name
is called, optionally withargs
(andkwargs
or ablk
), and returnsretval
. -
#initialize(delegator = nil) ⇒ Mock
constructor
:nodoc:.
-
#method_missing(sym, *args, **kwargs, &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:
53 54 55 56 57 |
# File 'lib/minitest/mock.rb', line 53 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, **kwargs, &block) ⇒ Object
:nodoc:
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 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/minitest/mock.rb', line 155 def method_missing sym, *args, **kwargs, &block # :nodoc: unless @expected_calls.key? sym then if @delegator && @delegator.respond_to?(sym) if kwargs.empty? then # FIX: drop this after 2.7 dead return @delegator.public_send(sym, *args, &block) else return @delegator.public_send(sym, *args, **kwargs, &block) end 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 %p" % [sym, args, kwargs] end expected_args, expected_kwargs, retval, val_block = expected_call.values_at :args, :kwargs, :retval, :block expected_kwargs = kwargs.to_h { |ak, av| [ak, Object] } if Hash == expected_kwargs if val_block then # keep "verify" happy @actual_calls[sym] << expected_call raise MockExpectationError, "mocked method %p failed block w/ %p %p" % [sym, args, kwargs] unless val_block.call(*args, **kwargs, &block) return retval end if expected_args.size != args.size then raise ArgumentError, "mocked method %p expects %d arguments, got %p" % [sym, expected_args.size, args] end if expected_kwargs.size != kwargs.size then raise ArgumentError, "mocked method %p expects %d keyword arguments, got %p" % [sym, expected_kwargs.size, kwargs] end zipped_args = expected_args.zip args fully_matched = zipped_args.all? { |mod, a| mod === a or mod == a } unless fully_matched then fmt = "mocked method %p called with unexpected arguments %p" raise MockExpectationError, fmt % [sym, args] end unless expected_kwargs.keys.sort == kwargs.keys.sort then fmt = "mocked method %p called with unexpected keywords %p vs %p" raise MockExpectationError, fmt % [sym, expected_kwargs.keys, kwargs.keys] end zipped_kwargs = expected_kwargs.to_h { |ek, ev| av = kwargs[ek] [ek, [ev, av]] } fully_matched = zipped_kwargs.all? { |ek, (ev, av)| ev === av or ev == av } unless fully_matched then fmt = "mocked method %p called with unexpected keyword arguments %p vs %p" raise MockExpectationError, fmt % [sym, expected_kwargs, kwargs] end @actual_calls[sym] << { :retval => retval, :args => zipped_args.map { |e, a| e === a ? e : a }, :kwargs => zipped_kwargs.to_h { |k, (e, a)| [k, e === a ? e : a] }, } retval end |
Instance Method Details
#__call(name, data) ⇒ Object
:nodoc:
125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/minitest/mock.rb', line 125 def __call name, data # :nodoc: case data when Hash then args = data[:args].inspect[1..-2] kwargs = data[:kwargs] if kwargs && !kwargs.empty? then args << ", " unless args.empty? args << kwargs.inspect[1..-2] end "#{name}(#{args}) => #{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 = [], **kwargs, &blk) ⇒ Object
Expect that method name
is called, optionally with args
(and kwargs
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"
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 |
# File 'lib/minitest/mock.rb', line 96 def expect name, retval, args = [], **kwargs, &blk name = name.to_sym if blk then raise ArgumentError, "args ignored when block given" unless args.empty? raise ArgumentError, "kwargs ignored when block given" unless kwargs.empty? @expected_calls[name] << { :retval => retval, :block => blk } else raise ArgumentError, "args must be an array" unless Array === args if ENV["MT_KWARGS_HAC\K"] && (Hash === args.last || Hash == args.last) then if kwargs.empty? then kwargs = args.pop else unless @@KW_WARNED then from = caller(1..1).first warn "Using MT_KWARGS_HAC\K yet passing kwargs. From #{from}" @@KW_WARNED = true end end end @expected_calls[name] << { :retval => retval, :args => args, :kwargs => kwargs } end self end |
#respond_to?(sym, include_private = false) ⇒ Boolean
:nodoc:
241 242 243 244 245 |
# File 'lib/minitest/mock.rb', line 241 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.
145 146 147 148 149 150 151 152 153 |
# File 'lib/minitest/mock.rb', line 145 def verify @expected_calls.each do |name, expected| actual = @actual_calls.fetch name, nil # defaults to [] 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 |