mcmire-matchy 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +36 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +162 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/config/hoe.rb +73 -0
- data/config/requirements.rb +15 -0
- data/countloc.rb +67 -0
- data/lib/matchy.rb +19 -0
- data/lib/matchy/built_in/change_expectations.rb +31 -0
- data/lib/matchy/built_in/enumerable_expectations.rb +41 -0
- data/lib/matchy/built_in/error_expectations.rb +74 -0
- data/lib/matchy/built_in/operator_expectations.rb +48 -0
- data/lib/matchy/built_in/truth_expectations.rb +146 -0
- data/lib/matchy/custom_matcher.rb +10 -0
- data/lib/matchy/expectation_builder.rb +9 -0
- data/lib/matchy/matcher_builder.rb +51 -0
- data/lib/matchy/modals.rb +34 -0
- data/lib/matchy/version.rb +9 -0
- data/setup.rb +1585 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/test/all.rb +7 -0
- data/test/ruby1.9.compatibility_tests.rb +541 -0
- data/test/test_change_expectation.rb +63 -0
- data/test/test_custom_matcher.rb +139 -0
- data/test/test_enumerable_expectations.rb +91 -0
- data/test/test_error_expectations.rb +156 -0
- data/test/test_expectation_builder.rb +28 -0
- data/test/test_helper.rb +1 -0
- data/test/test_matcher_builder.rb +72 -0
- data/test/test_modals.rb +39 -0
- data/test/test_operator_expectations.rb +167 -0
- data/test/test_truth_expectations.rb +373 -0
- metadata +104 -0
data/lib/matchy.rb
ADDED
@@ -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,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)
|