From cd5d158510fa4ad5dcfb2976361d856eb48ec5bf Mon Sep 17 00:00:00 2001 From: Matthias Hennemeyer Date: Fri, 6 Feb 2009 14:10:59 +0100 Subject: [PATCH] Refactored all matchers to use Matchy::MatcherBuilder#build_matcher(). --- .../built_in/enumerable_expectations.rb | 51 +---- lib/matchy/built_in/error_expectations.rb | 102 +++------ lib/matchy/built_in/operator_expectations.rb | 68 +----- lib/matchy/built_in/truth_expectations.rb | 208 ++++++------------ lib/matchy/expectation.rb | 2 +- lib/matchy/matcher_builder.rb | 1 + test/test_modals.rb | 4 +- test/test_truth_expectations.rb | 8 + 8 files changed, 128 insertions(+), 316 deletions(-) diff --git a/lib/matchy/built_in/enumerable_expectations.rb b/lib/matchy/built_in/enumerable_expectations.rb index d729f9f..4d326f6 100644 --- a/lib/matchy/built_in/enumerable_expectations.rb +++ b/lib/matchy/built_in/enumerable_expectations.rb @@ -1,44 +1,7 @@ module Matchy module Expectations - class IncludeExpectation < Base - def matches?(receiver) - @receiver = receiver - @expected.each do |o| - return false unless receiver.include?(o) - end - - true - end - - def failure_message - "Expected #{@receiver.inspect} to include #{@expected.inspect}." - end - - def negative_failure_message - "Expected #{@receiver.inspect} to not include #{@expected.inspect}." - end - end - - class ExcludeExpectation < Base - def matches?(receiver) - @receiver = receiver - @expected.each do |o| - return false unless !receiver.include?(o) - end - - true - end - - def failure_message - "Expected #{@receiver.inspect} to exclude #{@expected.inspect}." - end - - def negative_failure_message - "Expected #{@receiver.inspect} to not exclude #{@expected.inspect}." - end - end - module TestCaseExtensions + # Calls +include?+ on the receiver for any object. You can also provide # multiple arguments to see if all of them are included. # @@ -49,7 +12,11 @@ module TestCaseExtensions # ['a', 'b', 'c'].should include('a', 'c') # def include(*obj) - Matchy::Expectations::IncludeExpectation.new(obj, self) + build_matcher(:include, obj) do |given, matcher, args| + matcher.positive_msg = "Expected #{given.inspect} to include #{args.inspect}." + matcher.negative_msg = "Expected #{given.inspect} to not include #{args.inspect}." + args.inject(true) {|m,o| m && given.include?(o) } + end end # Expects the receiver to exclude the given object(s). You can provide @@ -62,7 +29,11 @@ def include(*obj) # ['a', 'b', 'c'].should exclude('e', 'f', 'g') # def exclude(*obj) - Matchy::Expectations::ExcludeExpectation.new(obj, self) + build_matcher(:exlude, obj) do |given, matcher, args| + matcher.positive_msg = "Expected #{given.inspect} to exclude #{args.inspect}." + matcher.negative_msg = "Expected #{given.inspect} to not exclude #{args.inspect}." + args.inject(true) {|m,o| m && !given.include?(o) } + end end end end diff --git a/lib/matchy/built_in/error_expectations.rb b/lib/matchy/built_in/error_expectations.rb index c29b860..97bb3ee 100644 --- a/lib/matchy/built_in/error_expectations.rb +++ b/lib/matchy/built_in/error_expectations.rb @@ -1,67 +1,5 @@ module Matchy module Expectations - class RaiseErrorExpectation < Base - def initialize(expected, test_case) - @error = nil - super - end - - def matches?(receiver) - @receiver = receiver - begin - receiver.call - return false - rescue StandardError => e - @error = e - return false unless e.class.ancestors.include?(@expected) - - return true - end - end - - def failure_message - extra = "" - if @error - extra = "but #{@error.class.name} was raised instead" - else - extra = "but none was raised" - end - - "Expected #{@receiver.inspect} to raise #{@expected.name}, #{extra}." - end - - def negative_failure_message - "Expected #{@receiver.inspect} to not raise #{@expected.name}." - end - end - - class ThrowSymbolExpectation < Base - def initialize(expected, test_case) - @thrown_symbol = nil - super - end - - def matches?(receiver) - @receiver = receiver - begin - receiver.call - rescue NameError => e - raise e unless e.message =~ /uncaught throw/ - @thrown_symbol = e.name.to_sym - ensure - return @expected == @thrown_symbol - end - end - - def failure_message - "Expected #{@receiver.inspect} to throw :#{@expected}, but #{@thrown_symbol ? ':' + @thrown_symbol.to_s + ' was thrown instead' : 'no symbol was thrown'}." - end - - def negative_failure_message - "Expected #{@receiver.inspect} to not throw :#{@expected}." - end - end - module TestCaseExtensions # Expects a lambda to raise an error. You can specify the error or leave it blank to encompass # any error. @@ -71,8 +9,23 @@ module TestCaseExtensions # lambda { raise "FAILURE." }.should raise_error # lambda { puts i_dont_exist }.should raise_error(NameError) # - def raise_error(obj = StandardError) - Matchy::Expectations::RaiseErrorExpectation.new(obj, self) + def raise_error(*obj) + build_matcher(:raise_error, obj) do |given, matcher, args| + raised = false + error = nil + begin + given.call + rescue StandardError => e + raised = true + error = e + end + extra = "but none was raised" + extra = "but #{error.class.name} was raised instead" if error + expected_error = args[0] || StandardError + matcher.positive_msg = "Expected #{given.inspect} to raise #{expected_error.name}, #{extra}." + matcher.negative_msg = "Expected #{given.inspect} to not raise #{expected_error.name}." + raised && error.class.ancestors.include?(expected_error) + end end # Expects a lambda to throw an error. @@ -82,8 +35,25 @@ def raise_error(obj = StandardError) # lambda { throw :thing }.should throw_symbol(:thing) # lambda { "not this time" }.should_not throw_symbol(:hello) # - def throw_symbol(obj) - Matchy::Expectations::ThrowSymbolExpectation.new(obj, self) + def throw_symbol(*obj) + build_matcher(:throw_symbol, obj) do |given, matcher, args| + raised = false + thrown_symbol = nil + begin + given.call + rescue NameError => e + raise e unless e.message =~ /uncaught throw/ + raised = true + thrown_symbol = e.name.to_sym + end + expected = args[0] + matcher.positive_msg = <<-END +Expected #{given.inspect} to throw :#{expected}, but \ +#{thrown_symbol ? ':' + thrown_symbol.to_s + ' was thrown instead' : 'no symbol was thrown'}. +END + matcher.negative_msg = "Expected #{given.inspect} to not throw :#{expected}." + expected == thrown_symbol + end end end end diff --git a/lib/matchy/built_in/operator_expectations.rb b/lib/matchy/built_in/operator_expectations.rb index 7f9e08f..1c45cdd 100644 --- a/lib/matchy/built_in/operator_expectations.rb +++ b/lib/matchy/built_in/operator_expectations.rb @@ -7,72 +7,18 @@ module Expectations # 13.should == 13 # "hello".length.should_not == 2 # - class OperatorExpectation < Base + class OperatorExpectation < Base + OPERATORS = ['==', '===', '=~', '>', '>=', '<', '<='] + def initialize(receiver, match) @receiver = receiver @match = match end - - def ==(expected) - @expected = expected - if @receiver.send(:==, expected) == @match - pass! - else - fail!("==") - end - end - - def ===(expected) - @expected = expected - if @receiver.send(:===, expected) == @match - pass! - else - fail!("===") - end - end - - def =~(expected) - @expected = expected - if @receiver.send(:=~, expected).nil? != @match - pass! - else - fail!("=~") - end - end - - def >(expected) - @expected = expected - if @receiver.send(:>, expected) == @match - pass! - else - fail!(">") - end - end - - def <(expected) - @expected = expected - if @receiver.send(:<, expected) == @match - pass! - else - fail!("<") - end - end - - def >=(expected) - @expected = expected - if @receiver.send(:>=, expected) == @match - pass! - else - fail!(">=") - end - end - def <=(expected) - @expected = expected - if @receiver.send(:<=, expected) == @match - pass! - else - fail!("<=") + OPERATORS.each do |op| + define_method(op) do |expected| + @expected = expected + (@receiver.send(op,expected) ? true : false) == @match ? pass! : fail!(op) end end diff --git a/lib/matchy/built_in/truth_expectations.rb b/lib/matchy/built_in/truth_expectations.rb index 06b265c..3d9d6c8 100644 --- a/lib/matchy/built_in/truth_expectations.rb +++ b/lib/matchy/built_in/truth_expectations.rb @@ -1,137 +1,5 @@ module Matchy module Expectations - class BeExpectation < Base - def matches?(receiver) - @receiver = receiver - - @expected == receiver - end - - def failure_message - "Expected #{@receiver.inspect} to be #{@expected.inspect}." - end - - def negative_failure_message - "Expected #{@receiver.inspect} to not be #{@expected.inspect}." - end - end - - class BeKindOfExpectation < Base - def matches?(receiver) - @receiver = receiver - @receiver.kind_of?(@expected) - end - - def failure_message - "Expected #{@receiver.inspect} to be kind of #{@expected.inspect}." - end - - def negative_failure_message - "Expected #{@receiver.inspect} to not be kind of #{@expected.inspect}." - end - end - - class BeCloseExpectation < Base - def initialize(expected, delta, test_case) - @expected = expected - @test_case = test_case - @delta = delta - end - - def matches?(receiver) - @receiver = receiver - - (receiver - @expected).abs < @delta - end - - def failure_message - "Expected #{@receiver.inspect} to be close to #{@expected.inspect} (delta: #{@delta})." - end - - def negative_failure_message - "Expected #{@receiver.inspect} to not be close to #{@expected.inspect} (delta: #{@delta})." - end - end - - class ExistExpectation < Base - def initialize(test_case) - @test_case = test_case - end - - def matches?(receiver) - @receiver = receiver - receiver.exist? - end - - def failure_message - "Expected #{@receiver.inspect} to exist." - end - - def negative_failure_message - "Expected #{@receiver.inspect} to not exist." - end - end - - class EqlExpectation < Base - def matches?(receiver) - @receiver = receiver - @expected.eql?(receiver) - end - - def failure_message - "Expected #{@receiver.inspect} to eql #{@expected.inspect}." - end - - def negative_failure_message - "Expected #{@receiver.inspect} to not eql #{@expected.inspect}." - end - end - - class EqualExpectation < Base - def matches?(receiver) - @receiver = receiver - @expected.equal?(receiver) - end - - def failure_message - "Expected #{@receiver.inspect} to equal #{@expected.inspect}." - end - - def negative_failure_message - "Expected #{@receiver.inspect} to not equal #{@expected.inspect}." - end - end - - class SatisfyExpectation < Base - def matches?(receiver) - @receiver = receiver - @expected.call(receiver) == true - end - - def failure_message - "Expected #{@receiver.inspect} to satisfy given block." - end - - def negative_failure_message - "Expected #{@receiver.inspect} to not satisfy given block." - end - end - - class RespondToExpectation < Base - def matches?(receiver) - @receiver = receiver - @receiver.respond_to?(@expected) - end - - def failure_message - "Expected #{@receiver.inspect} to respond to #{@expected.inspect}." - end - - def negative_failure_message - "Expected #{@receiver.inspect} to not respond to #{@expected.inspect}." - end - end - module TestCaseExtensions # Simply checks if the receiver matches the expected object. # TODO: Fill this out to implement much of the RSpec functionality (and then some) @@ -141,8 +9,14 @@ module TestCaseExtensions # "hello".should be("hello") # (13 < 20).should be(true) # - def be(obj) - Matchy::Expectations::BeExpectation.new(obj, self) + def be(*obj) + build_matcher(:be, obj) do |receiver, matcher, args| + @receiver = receiver + @expected = args[0] + matcher.positive_msg = "Expected #{@receiver.inspect} to be #{@expected.inspect}." + matcher.negative_msg = "Expected #{@receiver.inspect} to not be #{@expected.inspect}." + @expected == @receiver + end end # Checks if the given object is kind_of? the expected class @@ -151,8 +25,14 @@ def be(obj) # # "hello".should be_kind_of(String) # 3.should be_kind_of(Fixnum) - def be_kind_of(klass) - Matchy::Expectations::BeKindOfExpectation.new(klass, self) + def be_kind_of(*klass) + build_matcher(:be_kind_of, klass) do |receiver, matcher, args| + @receiver = receiver + @expected = args[0] + matcher.positive_msg = "Expected #{@receiver.inspect} to be kind of #{@expected.inspect}." + matcher.negative_msg = "Expected #{@receiver.inspect} to not be kind of #{@expected.inspect}." + @receiver.kind_of?(@expected) + end end # Checks if the given object is within a given object and delta. @@ -163,7 +43,14 @@ def be_kind_of(klass) # (13.0 - 4.0).should be_close(9.0, 0.5) # def be_close(obj, delta = 0.3) - Matchy::Expectations::BeCloseExpectation.new(obj, delta, self) + build_matcher(:be_close, [obj, delta]) do |receiver, matcher, args| + @receiver = receiver + @expected = args[0] + @delta = args[1] + matcher.positive_msg = "Expected #{@receiver.inspect} to be close to #{@expected.inspect} (delta: #{@delta})." + matcher.negative_msg = "Expected #{@receiver.inspect} to not be close to #{@expected.inspect} (delta: #{@delta})." + (@receiver - @expected).abs < @delta + end end # Calls +exist?+ on the given object. @@ -174,7 +61,12 @@ def be_close(obj, delta = 0.3) # found_user.should exist # def exist - Matchy::Expectations::ExistExpectation.new(self) + build_matcher(:exist) do |receiver, matcher, args| + @receiver = receiver + matcher.positive_msg = "Expected #{@receiver.inspect} to exist." + matcher.negative_msg = "Expected #{@receiver.inspect} to not exist." + receiver.exist? + end end # Calls +eql?+ on the given object (i.e., are the objects the same value?) @@ -184,8 +76,14 @@ def exist # 1.should_not eql(1.0) # (12 / 6).should eql(6) # - def eql(obj) - Matchy::Expectations::EqlExpectation.new(obj, self) + def eql(*obj) + build_matcher(:eql, obj) do |receiver, matcher, args| + @receiver = receiver + @expected = args[0] + matcher.positive_msg = "Expected #{@receiver.inspect} to eql #{@expected.inspect}." + matcher.negative_msg = "Expected #{@receiver.inspect} to not eql #{@expected.inspect}." + @expected.eql?(@receiver) + end end # Calls +equal?+ on the given object (i.e., do the two objects have the same +object_id+?) @@ -201,8 +99,14 @@ def eql(obj) # # The same object_id # x[0].should equal(y[0]) # - def equal(obj) - Matchy::Expectations::EqualExpectation.new(obj, self) + def equal(*obj) + build_matcher(:equal, obj) do |receiver, matcher, args| + @receiver = receiver + @expected = args[0] + matcher.positive_msg = "Expected #{@receiver.inspect} to equal #{@expected.inspect}." + matcher.negative_msg = "Expected #{@receiver.inspect} to not equal #{@expected.inspect}." + @expected.equal?(@receiver) + end end # A last ditch way to implement your testing logic. You probably shouldn't use this unless you @@ -213,8 +117,14 @@ def equal(obj) # (13 - 4).should satisfy(lambda {|i| i < 20}) # "hello".should_not satisfy(lambda {|s| s =~ /hi/}) # - def satisfy(obj) - Matchy::Expectations::SatisfyExpectation.new(obj, self) + def satisfy(*obj) + build_matcher(:satisfy, obj) do |receiver, matcher, args| + @receiver = receiver + @expected = args[0] + matcher.positive_msg = "Expected #{@receiver.inspect} to satisfy given block." + matcher.negative_msg = "Expected #{@receiver.inspect} to not satisfy given block." + @expected.call(@receiver) == true + end end # Checks if the given object responds to the given method @@ -223,8 +133,14 @@ def satisfy(obj) # # "foo".should respond_to(:length) # {}.should respond_to(:has_key?) - def respond_to(meth) - Matchy::Expectations::RespondToExpectation.new(meth, self) + def respond_to(*meth) + build_matcher(:respond_to, meth) do |receiver, matcher, args| + @receiver = receiver + @expected = args[0] + matcher.positive_msg = "Expected #{@receiver.inspect} to respond to #{@expected.inspect}." + matcher.negative_msg = "Expected #{@receiver.inspect} to not respond to #{@expected.inspect}." + @receiver.respond_to?(@expected) + end end end end diff --git a/lib/matchy/expectation.rb b/lib/matchy/expectation.rb index 6811160..56fef2b 100644 --- a/lib/matchy/expectation.rb +++ b/lib/matchy/expectation.rb @@ -3,7 +3,7 @@ module Expectations # Base class for all expectations. Inheriting from this DRYs up a lot of the # constructor logic, etc. # - # TODO: Implement failure messages, inluding negative failure messages. Also, + # TODO: Implement failure messages, including negative failure messages. Also, # need to name variables/parameters to go with testing nomenclature (expected rather # than just "object"). class Base diff --git a/lib/matchy/matcher_builder.rb b/lib/matchy/matcher_builder.rb index 9130589..b22c918 100644 --- a/lib/matchy/matcher_builder.rb +++ b/lib/matchy/matcher_builder.rb @@ -1,6 +1,7 @@ module Matchy module MatcherBuilder def build_matcher(matcher_name=nil, args=[], &block) + #args = [args] #unless args.kind_of?(Array) match_block = lambda do |actual, matcher| block.call(actual, matcher, args) end diff --git a/test/test_modals.rb b/test/test_modals.rb index 507f455..db17b17 100644 --- a/test/test_modals.rb +++ b/test/test_modals.rb @@ -2,8 +2,8 @@ class TestModals < Test::Unit::TestCase def setup - @expectation = Matchy::Expectations::EqlExpectation.new(3, self) - @bad_expectation = Matchy::Expectations::EqlExpectation.new(4, self) + @expectation = build_matcher() {|r,m,a| true} + @bad_expectation = build_matcher() {|r,m,a| false} end def test_should diff --git a/test/test_truth_expectations.rb b/test/test_truth_expectations.rb index e588b03..1715849 100644 --- a/test/test_truth_expectations.rb +++ b/test/test_truth_expectations.rb @@ -37,6 +37,10 @@ def test_eql 3.should eql(3) end + def test_eql_array + [1,2,3].should eql([1,2,3]) + end + def test_negative_eql 3.should_not eql(9) end @@ -81,6 +85,10 @@ def test_be true.should be(true) end + def test_be_array + [1,2,3].should be([1,2,3]) + end + def test_negative_be true.should_not be(false) end