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 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 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
|
#!/usr/bin/env ruby
#---
# Copyright 2003-2013 by Jim Weirich (jim.weirich@gmail.com).
# All rights reserved.
#
# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#+++
require 'flexmock/errors'
require 'flexmock/ordering'
require 'flexmock/argument_matching'
require 'flexmock/explicit_needed'
require 'flexmock/class_extensions'
require 'flexmock/expectation_builder'
require 'flexmock/call_validator'
require 'flexmock/call_record'
# FlexMock is a flexible mock object framework for creating and using
# test doubles (mocks, stubs and spies).
#
# Basic Usage:
#
# m = flexmock("name")
# m.should_receive(:upcase).with("stuff").
# and_return("STUFF")
# m.should_receive(:downcase).with(String).
# and_return { |s| s.downcase }.once
#
# With Test::Unit Integration:
#
# class TestSomething < Test::Unit::TestCase
# def test_something
# m = flexmock("name")
# m.should_receive(:hi).and_return("Hello")
# m.hi
# end
# end
#
# Note: Also, if you override +teardown+, make sure you call +super+.
#
class FlexMock
include Ordering
attr_reader :flexmock_name
attr_reader :flexmock_container_stack
class << self
attr_accessor :partials_are_based
attr_accessor :partials_verify_signatures
end
self.partials_are_based = false
self.partials_verify_signatures = false
# Null object for {#parent_mock}
class NullParentMock
def flexmock_expectations_for(sym)
end
end
# Create a FlexMock object with the given name. The name is used in
# error messages. If no container is given, create a new, one-off
# container for this mock.
def initialize(name="unknown", container=nil, parent: nil)
@flexmock_name = name
@flexmock_closed = false
@flexmock_container_stack = Array.new
@expectations = Hash.new
@verified = false
@calls = []
@base_class = nil
if parent
@ignore_missing = parent.ignore_missing?
@parent_mock = parent
else
@ignore_missing = false
@parent_mock = NullParentMock.new
end
container = UseContainer.new if container.nil?
container.flexmock_remember(self)
end
def ignore_missing?
@ignore_missing
end
def flexmock_container
flexmock_container_stack.last
end
def push_flexmock_container(container)
flexmock_container_stack.push(container)
end
def pop_flexmock_container
flexmock_container_stack.pop
end
# Return the inspection string for a mock.
def inspect
"<FlexMock:#{flexmock_name}>"
end
# Verify that each method that had an explicit expected count was
# actually called that many times.
def flexmock_verify
return if @verified
@verified = true
flexmock_wrap do
@expectations.each do |sym, handler|
handler.flexmock_verify
end
end
end
# Teardown and infrastructure setup for this mock.
def flexmock_teardown
@flexmock_closed = true
end
def flexmock_closed?
@flexmock_closed
end
# Ignore all undefined (missing) method calls.
def should_ignore_missing
@ignore_missing = true
self
end
alias mock_ignore_missing should_ignore_missing
def by_default
@last_expectation.by_default
self
end
# Handle missing methods by attempting to look up a handler.
def method_missing(sym, *args, **kw, &block)
FlexMock.verify_mocking_allowed!
call_record = CallRecord.new(sym, args, kw, block)
@calls << call_record
flexmock_wrap do
if flexmock_closed?
FlexMock.undefined
elsif exp = flexmock_expectations_for(sym)
exp.call(args, kw, block, call_record)
elsif @base_class && @base_class.flexmock_defined?(sym)
FlexMock.undefined
elsif @ignore_missing
FlexMock.undefined
else
super(sym, *args, **kw, &block)
end
end
end
# Save the original definition of respond_to? for use a bit later.
alias flexmock_respond_to? respond_to?
# Override the built-in respond_to? to include the mocked methods.
def respond_to?(sym, *args)
super || (@expectations[sym] ? true : @ignore_missing)
end
# Find the mock expectation for method sym and arguments.
def flexmock_find_expectation(method_name, *args, **kw, &block) # :nodoc:
if exp = flexmock_expectations_for(method_name)
exp.find_expectation(args, kw, block)
end
end
# Return the expectation director for a method name.
def flexmock_expectations_for(method_name) # :nodoc:
@expectations[method_name] || @parent_mock.flexmock_expectations_for(method_name)
end
def flexmock_base_class
@base_class
end
def flexmock_based_on(base_class)
@base_class = base_class
if base_class <= Kernel
if self.class != base_class
should_receive(:class => base_class)
should_receive(:kind_of?).and_return { |against| base_class <= against }
end
end
end
CALL_VALIDATOR = CallValidator.new
# True if the mock received the given method and arguments.
def flexmock_received?(method_name, args, kw, options = {})
CALL_VALIDATOR.received?(@calls, method_name, args, kw, options)
end
# Return the list of calls made on this mock. Used in formatting
# error messages.
def flexmock_calls
@calls
end
# Invocke the original non-mocked functionality for the given
# symbol.
def flexmock_invoke_original(method_name, args, kw = {}, orig_block = nil)
return FlexMock.undefined
end
# Override the built-in +method+ to include the mocked methods.
def method(method_name)
if (expectations = flexmock_expectations_for(method_name))
->(*args, **kw, &block) { expectations.call(args, kw, block) }
else
super
end
rescue NameError => ex
if ignore_missing?
proc { FlexMock.undefined }
else
raise ex
end
end
# :call-seq:
# mock.should_receive(:method_name)
# mock.should_receive(:method1, method2, ...)
# mock.should_receive(:meth1 => result1, :meth2 => result2, ...)
#
# Declare that the mock object should receive a message with the given name.
#
# If more than one method name is given, then the mock object should expect
# to receive all the listed melthods. If a hash of method name/value pairs
# is given, then the each method will return the associated result. Any
# expectations applied to the result of +should_receive+ will be applied to
# all the methods defined in the argument list.
#
# An expectation object for the method name is returned as the result of
# this method. Further expectation constraints can be added by chaining to
# the result.
#
# See Expectation for a list of declarators that can be used.
#
def should_receive(*args, **kw)
flexmock_define_expectation(caller, *args, **kw)
end
ON_RUBY_20 = (RUBY_VERSION =~ /^2\.0\./)
# Using +location+, define the expectations specified by +args+.
def flexmock_define_expectation(location, *args, **kw)
@last_expectation = EXP_BUILDER.parse_should_args(self, args, kw) do |method_name|
exp = flexmock_expectations_for(method_name) || ExpectationDirector.new(method_name)
@expectations[method_name] = exp
result = Expectation.new(self, method_name, location)
exp << result
override_existing_method(method_name) if flexmock_respond_to?(method_name, true)
if @base_class && !@base_class.flexmock_defined?(method_name)
if !ON_RUBY_20 || !@base_class.ancestors.include?(Class)
result = ExplicitNeeded.new(result, method_name, @base_class)
end
end
result
end
end
# Declare that the mock object should expect methods by providing a
# recorder for the methods and having the user invoke the expected
# methods in a block. Further expectations may be applied the
# result of the recording call.
#
# Example Usage:
#
# mock.should_expect do |record|
# record.add(Integer, 4) { |a, b|
# a + b
# }.at_least.once
#
def should_expect
yield Recorder.new(self)
end
private
# Wrap a block of code so the any assertion errors are wrapped so
# that the mock name is added to the error message .
def flexmock_wrap(&block)
yield
rescue FlexMock.framework_adapter.assertion_failed_error, FlexMock.framework_adapter.check_failed_error => ex
raise ex, "in mock '#{@flexmock_name}': #{ex.message}", ex.backtrace
end
# Override the existing definition of method +method_name+ in the
# mock. Most methods depend on the method_missing trick to be
# invoked. However, if the method already exists, it will not call
# method_missing. This method defines a singleton method on the mock
# to explicitly invoke the method_missing logic.
def override_existing_method(method_name)
sclass.class_eval <<-EOS
def #{method_name}(*args, **kw, &block)
method_missing(:#{method_name}, *args, **kw, &block)
end
EOS
end
# Return the singleton class of the mock object.
def sclass
class << self; self; end
end
end
require 'flexmock/core_class_methods'
|