lib/rspec/expectations/expectation_target.rb
module RSpec module Expectations # Wraps the target of an expectation. # # @example # expect(something) # => ExpectationTarget wrapping something # expect { do_something } # => ExpectationTarget wrapping the block # # # used with `to` # expect(actual).to eq(3) # # # with `not_to` # expect(actual).not_to eq(3) # # @note `ExpectationTarget` is not intended to be instantiated # directly by users. Use `expect` instead. class ExpectationTarget # @private # Used as a sentinel value to be able to tell when the user # did not pass an argument. We can't use `nil` for that because # `nil` is a valid value to pass. UndefinedValue = Module.new # @note this name aligns with `Minitest::Expectation` so that our # {InstanceMethods} module can be included in that class when # used in a Minitest context. # @return [Object] the target of the expectation attr_reader :target # @api private def initialize(value) @target = value end # @private def self.for(value, block) if UndefinedValue.equal?(value) unless block raise ArgumentError, "You must pass either an argument or a block to `expect`." end BlockExpectationTarget.new(block) elsif block raise ArgumentError, "You cannot pass both an argument and a block to `expect`." else ValueExpectationTarget.new(value) end end # Defines instance {ExpectationTarget} instance methods. These are defined # in a module so we can include it in `Minitest::Expectation` when # `rspec/expectations/minitest_integration` is loaded in order to # support usage with Minitest. module InstanceMethods # Runs the given expectation, passing if `matcher` returns true. # @example # expect(value).to eq(5) # expect { perform }.to raise_error # @param [Matcher] # matcher # @param [String, Proc] message optional message to display when the expectation fails # @return [Boolean] true if the expectation succeeds (else raises) # @see RSpec::Matchers def to(matcher=nil, message=nil, &block) prevent_operator_matchers(:to) unless matcher RSpec::Expectations::PositiveExpectationHandler.handle_matcher(target, matcher, message, &block) end # Runs the given expectation, passing if `matcher` returns false. # @example # expect(value).not_to eq(5) # @param [Matcher] # matcher # @param [String, Proc] message optional message to display when the expectation fails # @return [Boolean] false if the negative expectation succeeds (else raises) # @see RSpec::Matchers def not_to(matcher=nil, message=nil, &block) prevent_operator_matchers(:not_to) unless matcher RSpec::Expectations::NegativeExpectationHandler.handle_matcher(target, matcher, message, &block) end alias to_not not_to private def prevent_operator_matchers(verb) raise ArgumentError, "The expect syntax does not support operator matchers, " \ "so you must pass a matcher to `##{verb}`." end end include InstanceMethods end # @private # Validates the provided matcher to ensure it supports block # expectations, in order to avoid user confusion when they # use a block thinking the expectation will be on the return # value of the block rather than the block itself. class ValueExpectationTarget < ExpectationTarget def to(matcher=nil, message=nil, &block) enforce_value_expectation(matcher) super end def not_to(matcher=nil, message=nil, &block) enforce_value_expectation(matcher) super end private def enforce_value_expectation(matcher) return if supports_value_expectations?(matcher) RSpec.deprecate( "expect(value).to #{RSpec::Support::ObjectFormatter.format(matcher)}", :message => "The implicit block expectation syntax is deprecated, you should pass " \ "a block rather than an argument to `expect` to use the provided " \ "block expectation matcher or the matcher must implement " \ "`supports_value_expectations?`. e.g `expect { value }.to " \ "#{RSpec::Support::ObjectFormatter.format(matcher)}` not " \ "`expect(value).to #{RSpec::Support::ObjectFormatter.format(matcher)}`" ) end def supports_value_expectations?(matcher) !matcher.respond_to?(:supports_value_expectations?) || matcher.supports_value_expectations? end end # @private # Validates the provided matcher to ensure it supports block # expectations, in order to avoid user confusion when they # use a block thinking the expectation will be on the return # value of the block rather than the block itself. class BlockExpectationTarget < ExpectationTarget def to(matcher, message=nil, &block) enforce_block_expectation(matcher) super end def not_to(matcher, message=nil, &block) enforce_block_expectation(matcher) super end alias to_not not_to private def enforce_block_expectation(matcher) return if supports_block_expectations?(matcher) raise ExpectationNotMetError, "You must pass an argument rather than a block to `expect` to use the provided " \ "matcher (#{RSpec::Support::ObjectFormatter.format(matcher)}), or the matcher must implement " \ "`supports_block_expectations?`." end def supports_block_expectations?(matcher) matcher.respond_to?(:supports_block_expectations?) && matcher.supports_block_expectations? end end end end