mcmire-matchy 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'test/unit'
5
+
6
+ require 'matchy/expectation_builder'
7
+ require 'matchy/modals'
8
+ require 'matchy/matcher_builder'
9
+ require 'matchy/custom_matcher'
10
+ require 'matchy/version'
11
+
12
+ require 'matchy/built_in/enumerable_expectations'
13
+ require 'matchy/built_in/error_expectations'
14
+ require 'matchy/built_in/truth_expectations'
15
+ require 'matchy/built_in/operator_expectations'
16
+ require 'matchy/built_in/change_expectations'
17
+
18
+ Test::Unit::TestCase.send(:include, Matchy::Expectations::TestCaseExtensions)
19
+ include Matchy::CustomMatcher
@@ -0,0 +1,31 @@
1
+ module Matchy
2
+ module Expectations
3
+ module TestCaseExtensions
4
+ # Checks if the given block alters the value of the block attached to change
5
+ #
6
+ # ==== Examples
7
+ # lambda {var += 1}.should change {var}.by(1)
8
+ # lambda {var += 2}.should change {var}.by_at_least(1)
9
+ # lambda {var += 1}.should change {var}.by_at_most(1)
10
+ # lambda {var += 2}.should change {var}.from(1).to(3) if var = 1
11
+ def change(&block)
12
+ build_matcher(:change) do |receiver, matcher, args|
13
+ before, done, after = block.call, receiver.call, block.call
14
+ comparison = after != before
15
+ if list = matcher.chained_messages
16
+ comparison = case list[0].name
17
+ # todo: provide meaningful messages
18
+ when :by then (after == before + list[0].args[0] || after == before - list[0].args[0])
19
+ when :by_at_least then (after >= before + list[0].args[0] || after <= before - list[0].args[0])
20
+ when :by_at_most then (after <= before + list[0].args[0] && after >= before - list[0].args[0])
21
+ when :from then (before == list[0].args[0]) && (after == list[1].args[0])
22
+ end
23
+ end
24
+ matcher.positive_failure_message = "given block shouldn't alter the block attached to change"
25
+ matcher.negative_failure_message = "given block should alter the block attached to change"
26
+ comparison
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,41 @@
1
+ module Matchy
2
+ module Expectations
3
+ module TestCaseExtensions
4
+
5
+ # Calls +include?+ on the receiver for any object. You can also provide
6
+ # multiple arguments to see if all of them are included.
7
+ #
8
+ # ==== Examples
9
+ #
10
+ # [1,2,3].should include(1)
11
+ # [7,8,8].should_not include(3)
12
+ # ['a', 'b', 'c'].should include('a', 'c')
13
+ #
14
+ def include(*obj)
15
+ _clude(:include, obj)
16
+ end
17
+
18
+ # Expects the receiver to exclude the given object(s). You can provide
19
+ # multiple arguments to see if all of them are included.
20
+ #
21
+ # ==== Examples
22
+ #
23
+ # [1,2,3].should exclude(16)
24
+ # [7,8,8].should_not exclude(7)
25
+ # ['a', 'b', 'c'].should exclude('e', 'f', 'g')
26
+ #
27
+ def exclude(*obj)
28
+ _clude(:exclude, obj)
29
+ end
30
+
31
+ private
32
+ def _clude(sym, obj)
33
+ build_matcher(sym, obj) do |given, matcher, args|
34
+ matcher.positive_failure_message = "Expected #{given.inspect} to #{sym} #{args.inspect}."
35
+ matcher.negative_failure_message = "Expected #{given.inspect} to not #{sym} #{args.inspect}."
36
+ args.inject(true) {|m,o| m && (given.include?(o) == (sym == :include)) }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,74 @@
1
+ module Matchy
2
+ module Expectations
3
+ module TestCaseExtensions
4
+ # Expects a lambda to raise an error. You can specify the error or leave it blank to encompass
5
+ # any error.
6
+ #
7
+ # ==== Examples
8
+ #
9
+ # lambda { raise "FAILURE." }.should raise_error
10
+ # lambda { puts i_dont_exist }.should raise_error(NameError)
11
+ #
12
+ def raise_error(*obj)
13
+ build_matcher(:raise_error, obj) do |receiver, matcher, args|
14
+ expected = args[0] || Exception
15
+ if args.size > 1
16
+ expected_message = args[1]
17
+ end
18
+
19
+ raised = false
20
+ error = nil
21
+ begin
22
+ receiver.call
23
+ rescue Exception => e
24
+ raised = true
25
+ error = e
26
+ end
27
+ if expected.respond_to?(:ancestors) && expected.ancestors.include?(Exception)
28
+ matcher.positive_failure_message = "Expected #{receiver.inspect} to raise #{expected.name}, " +
29
+ (error ? "but #{error.class.name} was raised instead." : "but none was raised.")
30
+ matcher.negative_failure_message = "Expected #{receiver.inspect} to not raise #{expected.name}."
31
+ comparison = (raised && error.class.ancestors.include?(expected))
32
+ else
33
+ expected_message = expected
34
+ end
35
+
36
+ if expected_message
37
+ message = error ? error.message : "none"
38
+ matcher.positive_failure_message = "Expected #{receiver.inspect} to raise error with message matching '#{expected_message}', but '#{message}' was raised."
39
+ matcher.negative_failure_message = "Expected #{receiver.inspect} to raise error with message not matching '#{expected_message}', but '#{message}' was raised."
40
+ comparison = (raised && (expected_message.kind_of?(Regexp) ? ((error.message =~ expected_message) ? true : false) : expected_message == error.message))
41
+ end
42
+ comparison
43
+ end
44
+ end
45
+
46
+ # Expects a lambda to throw an error.
47
+ #
48
+ # ==== Examples
49
+ #
50
+ # lambda { throw :thing }.should throw_symbol(:thing)
51
+ # lambda { "not this time" }.should_not throw_symbol(:hello)
52
+ #
53
+ def throw_symbol(*obj)
54
+ build_matcher(:throw_symbol, obj) do |receiver, matcher, args|
55
+ raised, thrown_symbol, expected = false, nil, args[0]
56
+ begin
57
+ receiver.call
58
+ rescue NameError => e
59
+ raise e unless e.message =~ /uncaught throw/
60
+ raised = true
61
+ thrown_symbol = e.name.to_sym if e.respond_to?(:name)
62
+ rescue ArgumentError => e
63
+ raise e unless e.message =~ /uncaught throw/
64
+ thrown_symbol = e.message.match(/uncaught throw :(.+)/)[1].to_sym
65
+ end
66
+ matcher.positive_failure_message = "Expected #{receiver.inspect} to throw :#{expected}, but " +
67
+ "#{thrown_symbol ? ':' + thrown_symbol.to_s + ' was thrown instead' : 'no symbol was thrown'}."
68
+ matcher.negative_failure_message = "Expected #{receiver.inspect} to not throw :#{expected}."
69
+ expected == thrown_symbol
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,48 @@
1
+ module Matchy
2
+ module Expectations
3
+ # Class to handle operator expectations.
4
+ #
5
+ # ==== Examples
6
+ #
7
+ # 13.should == 13
8
+ # "hello".length.should_not == 2
9
+ #
10
+ class OperatorExpectation #< Base
11
+ include Test::Unit::Assertions
12
+
13
+ def initialize(receiver, match)
14
+ @receiver, @match = receiver, match
15
+ end
16
+
17
+ ['==', '===', '=~', '>', '>=', '<', '<='].each do |op|
18
+ define_method(op) do |expected|
19
+ @expected = expected
20
+ (@receiver.send(op,expected) ? true : false) == @match ? pass! : fail!(op)
21
+ end
22
+ end
23
+
24
+ protected
25
+ def pass!
26
+ defined?($current_test_case) ? $current_test_case.assert(true) : (assert true)
27
+ end
28
+
29
+ def fail!(operator)
30
+ flunk @match ? failure_message(operator) : negative_failure_message(operator)
31
+ end
32
+
33
+ def failure_message(operator)
34
+ out = "Expected #{@receiver.inspect} to #{operator} #{@expected.inspect}"
35
+ if Hash === @receiver && Hash === @expected
36
+ # Hash#diff, from ActiveSupport
37
+ diff = @receiver.dup.delete_if { |k, v| @expected[k] == v }.merge(@expected.dup.delete_if { |k, v| @receiver.has_key?(k) })
38
+ out += " (diff: #{diff.inspect})"
39
+ end
40
+ out += "."
41
+ end
42
+
43
+ def negative_failure_message(operator)
44
+ "Expected #{@receiver.inspect} to not #{operator} #{@expected.inspect}."
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,146 @@
1
+ module Matchy
2
+ module Expectations
3
+ module TestCaseExtensions
4
+ # Simply checks if the receiver matches the expected object.
5
+ # TODO: Fill this out to implement much of the RSpec functionality (and then some)
6
+ #
7
+ # ==== Examples
8
+ #
9
+ # "hello".should be("hello")
10
+ # (13 < 20).should be(true)
11
+ #
12
+ def be(*obj)
13
+ build_matcher(:be, obj) do |receiver, matcher, args|
14
+ @receiver, expected = receiver, args[0]
15
+ matcher.positive_failure_message = "Expected #{@receiver.inspect} to be #{expected.inspect}."
16
+ matcher.negative_failure_message = "Expected #{@receiver.inspect} to not be #{expected.inspect}."
17
+ expected == @receiver
18
+ end
19
+ end
20
+
21
+ # Checks if the given object is within a given object and delta.
22
+ #
23
+ # ==== Examples
24
+ #
25
+ # (20.0 - 2.0).should be_close(18.0)
26
+ # (13.0 - 4.0).should be_close(9.0, 0.5)
27
+ #
28
+ def be_close(obj, delta = 0.3)
29
+ build_matcher(:be_close, [obj, delta]) do |receiver, matcher, args|
30
+ @receiver, expected, delta = receiver, args[0], args[1]
31
+ matcher.positive_failure_message = "Expected #{@receiver.inspect} to be close to #{expected.inspect} (delta: #{delta})."
32
+ matcher.negative_failure_message = "Expected #{@receiver.inspect} to not be close to #{expected.inspect} (delta: #{delta})."
33
+ (@receiver - expected).abs < delta
34
+ end
35
+ end
36
+
37
+ # Calls +exist?+ on the given object.
38
+ #
39
+ # ==== Examples
40
+ #
41
+ # # found_user.exist?
42
+ # found_user.should exist
43
+ #
44
+ def exist
45
+ ask_for(:exist, :with_arg => nil)
46
+ end
47
+
48
+ # Calls +eql?+ on the given object (i.e., are the objects the same value?)
49
+ #
50
+ # ==== Examples
51
+ #
52
+ # 1.should_not eql(1.0)
53
+ # (12 / 6).should eql(6)
54
+ #
55
+ def eql(*obj)
56
+ ask_for(:eql, :with_arg => obj)
57
+ end
58
+
59
+ # Calls +equal?+ on the given object (i.e., do the two objects have the same +object_id+?)
60
+ #
61
+ # ==== Examples
62
+ #
63
+ # x = [1,2,3]
64
+ # y = [1,2,3]
65
+ #
66
+ # # Different object_id's...
67
+ # x.should_not equal(y)
68
+ #
69
+ # # The same object_id
70
+ # x[0].should equal(y[0])
71
+ #
72
+ def equal(*obj)
73
+ ask_for(:equal, :with_arg => obj)
74
+ end
75
+
76
+ # A last ditch way to implement your testing logic. You probably shouldn't use this unless you
77
+ # have to.
78
+ #
79
+ # ==== Examples
80
+ #
81
+ # (13 - 4).should satisfy(lambda {|i| i < 20})
82
+ # "hello".should_not satisfy(lambda {|s| s =~ /hi/})
83
+ #
84
+ def satisfy(*obj)
85
+ build_matcher(:satisfy, obj) do |receiver, matcher, args|
86
+ @receiver, expected = receiver, args[0]
87
+ matcher.positive_failure_message = "Expected #{@receiver.inspect} to satisfy given block."
88
+ matcher.negative_failure_message = "Expected #{@receiver.inspect} to not satisfy given block."
89
+ expected.call(@receiver) == true
90
+ end
91
+ end
92
+
93
+ # Checks if the given object responds to the given method
94
+ #
95
+ # ==== Examples
96
+ #
97
+ # "foo".should respond_to(:length)
98
+ # {}.should respond_to(:has_key?)
99
+ def respond_to(*meth)
100
+ ask_for(:respond_to, :with_arg => meth)
101
+ end
102
+
103
+ # Asks given for success?().
104
+ # This is necessary because Rails Integration::Session
105
+ # overides method_missing without grace.
106
+ #
107
+ # ==== Examples
108
+ #
109
+ # @response.should be_success
110
+ def be_success
111
+ ask_for(:success, :with_arg => nil)
112
+ end
113
+
114
+ alias_method :old_missing, :method_missing
115
+ # ==be_*something(*args)
116
+ #
117
+ # ===This method_missing acts as a matcher builder.
118
+ # If a call to be_xyz() reaches this method_missing (say: obj.should be_xyz),
119
+ # a matcher with the name xyz will be built, whose defining property
120
+ # is that it returns the value of obj.xyz? for matches?.
121
+ # ==== Examples
122
+ #
123
+ # nil.should be_nil
124
+ # 17.should be_kind_of(Fixnum)
125
+ # obj.something? #=> true
126
+ # obj.should be_something
127
+ def method_missing(name, *args, &block)
128
+ if (name.to_s =~ /^be_(.+)/)
129
+ ask_for($1, :with_arg => args)
130
+ else
131
+ old_missing(name, *args, &block)
132
+ end
133
+ end
134
+
135
+ private
136
+ def ask_for(sym, option={})
137
+ build_matcher(sym, (option[:with_arg] || [])) do |receiver, matcher, args|
138
+ expected, meth = args[0], (sym.to_s + "?" ).to_sym
139
+ matcher.positive_failure_message = "Expected #{receiver.inspect} to return true for #{sym}?, with '#{(expected && expected.inspect) || 'no args'}'."
140
+ matcher.negative_failure_message = "Expected #{receiver.inspect} to not return true for #{sym}?, with '#{(expected && expected.inspect) || 'no args'}'."
141
+ expected ? receiver.send(meth, expected) : receiver.send(meth)
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,10 @@
1
+ module Matchy
2
+ module CustomMatcher
3
+ include Matchy::MatcherBuilder
4
+ def custom_matcher(matcher_name, &block)
5
+ define_method matcher_name do |*args|
6
+ build_matcher(matcher_name, args, &block)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module Matchy
2
+ module ExpectationBuilder
3
+ def self.build_expectation(match, exp, obj)
4
+ return Matchy::Expectations::OperatorExpectation.new(obj, match) unless exp
5
+
6
+ (exp.matches?(obj) != match) ? exp.fail!(match) : exp.pass!(match)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,51 @@
1
+ module Matchy
2
+ module MatcherBuilder
3
+ class ChainedMessage < Struct.new(:name, :args, :block); end
4
+
5
+ def build_matcher(matcher_name=nil, args=[], &block)
6
+ match_block = lambda do |actual, matcher|
7
+ block.call(actual, matcher, args)
8
+ end
9
+
10
+ body = lambda do |klass|
11
+ include Test::Unit::Assertions
12
+ @matcher_name = matcher_name.to_s
13
+
14
+ def self.matcher_name
15
+ @matcher_name
16
+ end
17
+
18
+ attr_reader :matcher_name
19
+ attr_accessor :positive_failure_message, :negative_failure_message, :chained_messages
20
+
21
+ def initialize(match_block, test_case)
22
+ @match_block, @test_case = match_block, test_case
23
+ @matcher_name = self.class.matcher_name
24
+ end
25
+
26
+ def method_missing(id, *args, &block)
27
+ (self.chained_messages ||= []) << ChainedMessage.new(id, args, block)
28
+ self
29
+ end
30
+
31
+ def matches?(given)
32
+ @positive_failure_message ||= "Matching with '#{matcher_name}' failed, although it should match."
33
+ @negative_failure_message ||= "Matching with '#{matcher_name}' passed, although it should_not match."
34
+ @match_block.call(given, self)
35
+ end
36
+
37
+ def fail!(which)
38
+ @test_case.flunk(which ? failure_message : negative_failure_message)
39
+ end
40
+
41
+ def pass!(which)
42
+ @test_case.assert true
43
+ end
44
+
45
+ alias_method :failure_message, :positive_failure_message
46
+ end
47
+
48
+ Class.new(&body).new(match_block, self)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,34 @@
1
+ module Matchy
2
+ module Modals
3
+ # Tests an expectation against the given object.
4
+ #
5
+ # ==== Examples
6
+ #
7
+ # "hello".should eql("hello")
8
+ # 13.should equal(13)
9
+ # lambda { raise "u r doomed" }.should raise_error
10
+ #
11
+ def should(expectation = nil)
12
+ Matchy::ExpectationBuilder.build_expectation(true, expectation, self)
13
+ end
14
+
15
+ alias :will :should
16
+
17
+ # Tests that an expectation doesn't match the given object.
18
+ #
19
+ # ==== Examples
20
+ #
21
+ # "hello".should_not eql("hi")
22
+ # 41.should_not equal(13)
23
+ # lambda { "savd bai da bell" }.should_not raise_error
24
+ #
25
+ def should_not(expectation = nil)
26
+ Matchy::ExpectationBuilder.build_expectation(false, expectation, self)
27
+ end
28
+
29
+ alias :will_not :should_not
30
+ alias :wont :should_not
31
+ end
32
+ end
33
+
34
+ Object.send(:include, Matchy::Modals)