lib/rspec/matchers/built_in/yield.rb in rspec-expectations-3.0.0.beta2 vs lib/rspec/matchers/built_in/yield.rb in rspec-expectations-3.0.0.rc1

- old
+ new

@@ -1,24 +1,34 @@ module RSpec module Matchers module BuiltIn + # @private + # Object that is yielded to `expect` when one of the + # yield matchers is used. Provides information about + # the yield behavior of the object-under-test. class YieldProbe def self.probe(block) - probe = new - assert_valid_expect_block!(block) + probe = new(block) + return probe unless probe.has_block? + probe.assert_valid_expect_block! block.call(probe) probe.assert_used! probe end attr_accessor :num_yields, :yielded_args - def initialize + def initialize(block) + @block = block @used = false self.num_yields, self.yielded_args = 0, [] end + def has_block? + Proc === @block + end + def to_proc @used = true probe = self Proc.new do |*args| @@ -56,86 +66,114 @@ "allows the matcher to detect whether or not the method-under-test " + "yields, and, if so, how many times, and what the yielded arguments " + "are." end - def self.assert_valid_expect_block!(block) - return if block.arity == 1 + def assert_valid_expect_block! + return if @block.arity == 1 raise "Your expect block must accept an argument to be used with this " + "matcher. Pass the argument as a block on to the method you are testing." end end + # @api private + # Provides the implementation for `yield_control`. + # Not intended to be instantiated directly. class YieldControl < BaseMatcher def initialize @expectation_type = nil @expected_yields_count = nil end - def matches?(block) - probe = YieldProbe.probe(block) - - if @expectation_type - probe.num_yields.send(@expectation_type, @expected_yields_count) - else - probe.yielded_once?(:yield_control) - end - end - + # @api public + # Specifies that the method is expected to yield once. def once exactly(1) self end + # @api public + # Specifies that the method is expected to yield once. def twice exactly(2) self end + # @api public + # Specifies that the method is expected to yield the given number of times. def exactly(number) set_expected_yields_count(:==, number) self end + # @api public + # Specifies the maximum number of times the method is expected to yield def at_most(number) set_expected_yields_count(:<=, number) self end + # @api public + # Specifies the minimum number of times the method is expected to yield def at_least(number) set_expected_yields_count(:>=, number) self end + # @api public + # No-op. Provides syntactic sugar. def times self end - def failure_message - 'expected given block to yield control'.tap do |failure_message| - failure_message << relativity_failure_message + # @private + def matches?(block) + @probe = YieldProbe.probe(block) + return false unless @probe.has_block? + + if @expectation_type + @probe.num_yields.__send__(@expectation_type, @expected_yields_count) + else + @probe.yielded_once?(:yield_control) end end + # @private + def does_not_match?(block) + !matches?(block) && @probe.has_block? + end + + # @api private + # @return [String] + def failure_message + 'expected given block to yield control' + failure_reason + end + + # @api private + # @return [String] def failure_message_when_negated - 'expected given block not to yield control'.tap do |failure_message| - failure_message << relativity_failure_message - end + 'expected given block not to yield control' + failure_reason end - private + # @private + def supports_block_expectations? + true + end + private + def set_expected_yields_count(relativity, n) @expectation_type = relativity @expected_yields_count = case n when Numeric then n when :once then 1 when :twice then 2 end end - def relativity_failure_message + def failure_reason + return " but was not a block" unless @probe.has_block? return '' unless @expected_yields_count " #{human_readable_expecation_type}#{human_readable_count}" end def human_readable_expecation_type @@ -153,79 +191,116 @@ else "#{@expected_yields_count} times" end end end + # @api private + # Provides the implementation for `yield_with_no_args`. + # Not intended to be instantiated directly. class YieldWithNoArgs < BaseMatcher - + # @private def matches?(block) @probe = YieldProbe.probe(block) + return false unless @probe.has_block? @probe.yielded_once?(:yield_with_no_args) && @probe.single_yield_args.empty? end + # @private + def does_not_match?(block) + !matches?(block) && @probe.has_block? + end + + # @private def failure_message - "expected given block to yield with no arguments, but #{failure_reason}" + "expected given block to yield with no arguments, but #{positive_failure_reason}" end + # @private def failure_message_when_negated - "expected given block not to yield with no arguments, but did" + "expected given block not to yield with no arguments, but #{negative_failure_reason}" end + # @private + def supports_block_expectations? + true + end + private - def failure_reason - if @probe.num_yields.zero? - "did not yield" - else - "yielded with arguments: #{@probe.single_yield_args.inspect}" - end + def positive_failure_reason + return "was not a block" unless @probe.has_block? + return "did not yield" if @probe.num_yields.zero? + "yielded with arguments: #{@probe.single_yield_args.inspect}" end + + def negative_failure_reason + return "was not a block" unless @probe.has_block? + "did" + end end + # @api private + # Provides the implementation for `yield_with_args`. + # Not intended to be instantiated directly. class YieldWithArgs include Composable def initialize(*args) @expected = args end + # @private def matches?(block) @probe = YieldProbe.probe(block) + return false unless @probe.has_block? @actual = @probe.single_yield_args @probe.yielded_once?(:yield_with_args) && args_match? end + # @private + def does_not_match?(block) + !matches?(block) && @probe.has_block? + end + + # @private def failure_message "expected given block to yield with arguments, but #{positive_failure_reason}" end + # @private def failure_message_when_negated "expected given block not to yield with arguments, but #{negative_failure_reason}" end + # @private def description desc = "yield with args" desc << "(#{expected_arg_description})" unless @expected.empty? desc end + # @private + def supports_block_expectations? + true + end + private def positive_failure_reason - if @probe.num_yields.zero? - "did not yield" - else - @positive_args_failure - end + return "was not a block" unless @probe.has_block? + return "did not yield" if @probe.num_yields.zero? + @positive_args_failure end def expected_arg_description @expected.map { |e| description_of e }.join(", ") end def negative_failure_reason - if all_args_match? + if !@probe.has_block? + "was not a block" + elsif all_args_match? "yielded with expected arguments" + "\nexpected not: #{surface_descriptions_in(@expected).inspect}" + "\n got: #{@actual.inspect}" else "did" @@ -250,48 +325,77 @@ def all_args_match? values_match?(@expected, @actual) end end + # @api private + # Provides the implementation for `yield_successive_args`. + # Not intended to be instantiated directly. class YieldSuccessiveArgs include Composable def initialize(*args) @expected = args end + # @private def matches?(block) @probe = YieldProbe.probe(block) + return false unless @probe.has_block? @actual = @probe.successive_yield_args args_match? end + def does_not_match?(block) + !matches?(block) && @probe.has_block? + end + + # @private def failure_message - "expected given block to yield successively with arguments, but yielded with unexpected arguments" + - "\nexpected: #{surface_descriptions_in(@expected).inspect}" + - "\n got: #{@actual.inspect}" + "expected given block to yield successively with arguments, but #{positive_failure_reason}" end + # @private def failure_message_when_negated - "expected given block not to yield successively with arguments, but yielded with expected arguments" + - "\nexpected not: #{surface_descriptions_in(@expected).inspect}" + - "\n got: #{@actual.inspect}" + "expected given block not to yield successively with arguments, but #{negative_failure_reason}" end + # @private def description desc = "yield successive args" desc << "(#{expected_arg_description})" desc end + # @private + def supports_block_expectations? + true + end + private def args_match? values_match?(@expected, @actual) end def expected_arg_description @expected.map { |e| description_of e }.join(", ") + end + + def positive_failure_reason + return "was not a block" unless @probe.has_block? + + "yielded with unexpected arguments" + + "\nexpected: #{surface_descriptions_in(@expected).inspect}" + + "\n got: #{@actual.inspect}" + end + + def negative_failure_reason + return "was not a block" unless @probe.has_block? + + "yielded with expected arguments" + + "\nexpected not: #{surface_descriptions_in(@expected).inspect}" + + "\n got: #{@actual.inspect}" end end end end end