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
|
RSpec::Support.require_rspec_mocks 'matchers/expectation_customization'
module RSpec
module Mocks
module Matchers
# @private
class Receive
include Matcher
def initialize(message, block)
@message = message
@block = block
@recorded_customizations = []
end
def matcher_name
"receive"
end
def description
describable.description_for("receive")
end
def setup_expectation(subject, &block)
warn_if_any_instance("expect", subject)
@describable = setup_mock_proxy_method_substitute(subject, :add_message_expectation, block)
end
alias matches? setup_expectation
def setup_negative_expectation(subject, &block)
# ensure `never` goes first for cases like `never.and_return(5)`,
# where `and_return` is meant to raise an error
@recorded_customizations.unshift ExpectationCustomization.new(:never, [], nil)
warn_if_any_instance("expect", subject)
setup_expectation(subject, &block)
end
alias does_not_match? setup_negative_expectation
def setup_allowance(subject, &block)
warn_if_any_instance("allow", subject)
setup_mock_proxy_method_substitute(subject, :add_stub, block)
end
def setup_any_instance_expectation(subject, &block)
setup_any_instance_method_substitute(subject, :should_receive, block)
end
def setup_any_instance_negative_expectation(subject, &block)
setup_any_instance_method_substitute(subject, :should_not_receive, block)
end
def setup_any_instance_allowance(subject, &block)
setup_any_instance_method_substitute(subject, :stub, block)
end
own_methods = (instance_methods - superclass.instance_methods)
MessageExpectation.public_instance_methods(false).each do |method|
next if own_methods.include?(method)
define_method(method) do |*args, &block|
@recorded_customizations << ExpectationCustomization.new(method, args, block)
self
end
ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
end
private
def describable
@describable ||= DefaultDescribable.new(@message)
end
def warn_if_any_instance(expression, subject)
return unless AnyInstance::Proxy === subject
RSpec.warning(
"`#{expression}(#{subject.klass}.any_instance).to` " \
"is probably not what you meant, it does not operate on " \
"any instance of `#{subject.klass}`. " \
"Use `#{expression}_any_instance_of(#{subject.klass}).to` instead."
)
end
def setup_mock_proxy_method_substitute(subject, method, block)
proxy = ::RSpec::Mocks.space.proxy_for(subject)
setup_method_substitute(proxy, method, block)
end
def setup_any_instance_method_substitute(subject, method, block)
proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject)
setup_method_substitute(proxy, method, block)
end
def setup_method_substitute(host, method, block, *args)
args << @message.to_sym
block = move_block_to_last_customization(block)
expectation = host.__send__(method, *args, &(@block || block))
@recorded_customizations.each do |customization|
customization.playback_onto(expectation)
end
expectation
end
def move_block_to_last_customization(block)
last = @recorded_customizations.last
return block unless last
last.block ||= block
nil
end
# MessageExpectation objects are able to describe themselves in detail.
# We use this as a fall back when a MessageExpectation is not available.
# @private
class DefaultDescribable
def initialize(message)
@message = message
end
# This is much simpler for the `any_instance` case than what the
# user may want, but I'm not up for putting a bunch of effort
# into full descriptions for `any_instance` expectations at this point :(.
def description_for(verb)
"#{verb} #{@message}"
end
end
end
end
end
end
|