module Matchy::Expectations class HaveExpectation < Base def initialize(expected, relativity=:exactly, test_case = nil) @expected = (expected == :no ? 0 : expected) @relativity = relativity @test_case = test_case end def relativities @relativities ||= { :exactly => "", :at_least => "at least ", :at_most => "at most " } end def matches?(collection_owner) if collection_owner.respond_to?(@collection_name) collection = collection_owner.__send__(@collection_name, *@args, &@block) elsif (@plural_collection_name && collection_owner.respond_to?(@plural_collection_name)) collection = collection_owner.__send__(@plural_collection_name, *@args, &@block) elsif (collection_owner.respond_to?(:length) || collection_owner.respond_to?(:size)) collection = collection_owner else collection_owner.__send__(@collection_name, *@args, &@block) end @given = collection.size if collection.respond_to?(:size) @given = collection.length if collection.respond_to?(:length) raise not_a_collection if @given.nil? return @given >= @expected if @relativity == :at_least return @given <= @expected if @relativity == :at_most return @given == @expected end def not_a_collection "expected #{@collection_name} to be a collection but it does not respond to #length or #size" end def failure_message "expected #{relative_expectation} #{@collection_name}, got #{@given}" end def negative_failure_message if @relativity == :exactly return "expected target not to have #{@expected} #{@collection_name}, got #{@given}" elsif @relativity == :at_most return <<-EOF Isn't life confusing enough? Instead of having to figure out the meaning of this: should_not have_at_most(#{@expected}).#{@collection_name} We recommend that you use this instead: should have_at_least(#{@expected + 1}).#{@collection_name} EOF elsif @relativity == :at_least return <<-EOF Isn't life confusing enough? Instead of having to figure out the meaning of this: should_not have_at_least(#{@expected}).#{@collection_name} We recommend that you use this instead: should have_at_most(#{@expected - 1}).#{@collection_name} EOF end end def description "have #{relative_expectation} #{@collection_name}" end def respond_to?(sym) @expected.respond_to?(sym) || super end private def method_missing(sym, *args, &block) @collection_name = sym if inflector = (defined?(ActiveSupport::Inflector) ? ActiveSupport::Inflector : (defined?(Inflector) ? Inflector : nil)) @plural_collection_name = inflector.pluralize(sym.to_s) end @args = args @block = block self end def relative_expectation "#{relativities[@relativity]}#{@expected}" end end module TestCaseExtensions def have(n) HaveExpectation.new(n, :exactly, self) end alias :have_exactly :have def have_at_least(n) HaveExpectation.new(n, :at_least, self) end def have_at_most(n) HaveExpectation.new(n, :at_most, self) end end end