shoulda-matchers 1.4.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile.lock +2 -2
- data/NEWS.md +3 -0
- data/README.md +60 -41
- data/gemfiles/3.0.gemfile.lock +11 -12
- data/gemfiles/3.1.gemfile.lock +16 -17
- data/gemfiles/3.2.gemfile.lock +17 -18
- data/lib/shoulda/matchers/active_model.rb +2 -0
- data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +33 -0
- data/lib/shoulda/matchers/active_model/only_integer_matcher.rb +33 -0
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +44 -25
- data/lib/shoulda/matchers/independent.rb +9 -0
- data/lib/shoulda/matchers/independent/delegate_matcher.rb +124 -0
- data/lib/shoulda/matchers/integrations/rspec.rb +5 -0
- data/lib/shoulda/matchers/integrations/test_unit.rb +11 -0
- data/lib/shoulda/matchers/version.rb +1 -1
- data/shoulda-matchers.gemspec +1 -1
- data/spec/shoulda/active_model/disallow_value_matcher_spec.rb +65 -0
- data/spec/shoulda/active_model/only_integer_matcher_spec.rb +69 -0
- data/spec/shoulda/active_model/validate_numericality_of_matcher_spec.rb +62 -12
- data/spec/shoulda/independent/delegate_matcher_spec.rb +183 -0
- metadata +28 -18
@@ -0,0 +1,33 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module Matchers
|
3
|
+
module ActiveModel # :nodoc:
|
4
|
+
class OnlyIntegerMatcher # :nodoc:
|
5
|
+
NON_INTEGER_VALUE = 0.1
|
6
|
+
|
7
|
+
def initialize(attribute)
|
8
|
+
@attribute = attribute
|
9
|
+
@disallow_value_matcher = DisallowValueMatcher.new(NON_INTEGER_VALUE).
|
10
|
+
for(attribute).
|
11
|
+
with_message(:not_an_integer)
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches?(subject)
|
15
|
+
@disallow_value_matcher.matches?(subject)
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_message(message)
|
19
|
+
@disallow_value_matcher.with_message(message)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def allowed_types
|
24
|
+
"integer"
|
25
|
+
end
|
26
|
+
|
27
|
+
def failure_message
|
28
|
+
@disallow_value_matcher.failure_message
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -17,55 +17,74 @@ module Shoulda # :nodoc:
|
|
17
17
|
ValidateNumericalityOfMatcher.new(attr)
|
18
18
|
end
|
19
19
|
|
20
|
-
class ValidateNumericalityOfMatcher
|
20
|
+
class ValidateNumericalityOfMatcher
|
21
|
+
NON_NUMERIC_VALUE = 'abcd'
|
22
|
+
|
21
23
|
def initialize(attribute)
|
22
|
-
|
24
|
+
@attribute = attribute
|
23
25
|
@options = {}
|
26
|
+
@submatchers = []
|
27
|
+
|
28
|
+
add_disallow_value_matcher
|
24
29
|
end
|
25
30
|
|
26
31
|
def only_integer
|
27
|
-
|
32
|
+
only_integer_matcher = OnlyIntegerMatcher.new(@attribute)
|
33
|
+
add_submatcher(only_integer_matcher)
|
28
34
|
self
|
29
35
|
end
|
30
36
|
|
31
37
|
def with_message(message)
|
32
|
-
|
33
|
-
@expected_message = message
|
34
|
-
end
|
38
|
+
@submatchers.each { |matcher| matcher.with_message(message) }
|
35
39
|
self
|
36
40
|
end
|
37
41
|
|
38
42
|
def matches?(subject)
|
39
|
-
|
40
|
-
|
43
|
+
@subject = subject
|
44
|
+
submatchers_match?
|
41
45
|
end
|
42
46
|
|
43
47
|
def description
|
44
|
-
"only allow #{
|
48
|
+
"only allow #{allowed_types} values for #{@attribute}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def failure_message
|
52
|
+
submatcher_failure_messages.last
|
45
53
|
end
|
46
54
|
|
47
55
|
private
|
48
56
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
57
|
+
def add_disallow_value_matcher
|
58
|
+
disallow_value_matcher = DisallowValueMatcher.new(NON_NUMERIC_VALUE).
|
59
|
+
for(@attribute).
|
60
|
+
with_message(:not_a_number)
|
61
|
+
|
62
|
+
add_submatcher(disallow_value_matcher)
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_submatcher(submatcher)
|
66
|
+
@submatchers << submatcher
|
67
|
+
end
|
68
|
+
|
69
|
+
def submatchers_match?
|
70
|
+
failing_submatchers.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
def submatcher_failure_messages
|
74
|
+
failing_submatchers.map(&:failure_message)
|
75
|
+
end
|
76
|
+
|
77
|
+
def failing_submatchers
|
78
|
+
@failing_submatchers ||= @submatchers.select { |matcher| !matcher.matches?(@subject) }
|
55
79
|
end
|
56
80
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
disallows_value_of(0.1, message)
|
61
|
-
else
|
62
|
-
true
|
63
|
-
end
|
81
|
+
def allowed_types
|
82
|
+
allowed = ["numeric"] + submatcher_allowed_types
|
83
|
+
allowed.join(", ")
|
64
84
|
end
|
65
85
|
|
66
|
-
def
|
67
|
-
|
68
|
-
disallows_value_of('abcd', message)
|
86
|
+
def submatcher_allowed_types
|
87
|
+
@submatchers.map(&:allowed_types).reject(&:empty?)
|
69
88
|
end
|
70
89
|
end
|
71
90
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'bourne'
|
2
|
+
|
3
|
+
module Shoulda # :nodoc:
|
4
|
+
module Matchers
|
5
|
+
module Independent # :nodoc:
|
6
|
+
|
7
|
+
# Ensure that a given method is delegated properly.
|
8
|
+
#
|
9
|
+
# Basic Syntax:
|
10
|
+
# it { should delegate_method(:deliver_mail).to(:mailman) }
|
11
|
+
#
|
12
|
+
# Options:
|
13
|
+
# * <tt>:as</tt> - tests that the object being delegated to is called with a certain method (defaults to same name as delegating method)
|
14
|
+
# * <tt>:with_arguments</tt> - tests that the method on the object being delegated to is called with certain arguments
|
15
|
+
#
|
16
|
+
# Examples:
|
17
|
+
# it { should delegate_method(:deliver_mail).to(:mailman).as(:deliver_with_haste)
|
18
|
+
# it { should delegate_method(:deliver_mail).to(:mailman).with_arguments('221B Baker St.', :hastily => true)
|
19
|
+
#
|
20
|
+
def delegate_method(delegating_method)
|
21
|
+
DelegateMatcher.new(delegating_method)
|
22
|
+
end
|
23
|
+
|
24
|
+
class DelegateMatcher
|
25
|
+
def initialize(delegating_method)
|
26
|
+
@delegating_method = delegating_method
|
27
|
+
end
|
28
|
+
|
29
|
+
def matches?(subject)
|
30
|
+
@subject = subject
|
31
|
+
ensure_target_method_is_present!
|
32
|
+
|
33
|
+
begin
|
34
|
+
extend Mocha::API
|
35
|
+
|
36
|
+
stubbed_object = stub(method_on_target)
|
37
|
+
subject.stubs(@target_method).returns(stubbed_object)
|
38
|
+
subject.send(@delegating_method)
|
39
|
+
|
40
|
+
stubbed_object.should have_received(method_on_target).with(*@delegated_arguments)
|
41
|
+
rescue NoMethodError, RSpec::Expectations::ExpectationNotMetError, Mocha::ExpectationError
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def does_not_match?(subject)
|
47
|
+
raise InvalidDelegateMatcher
|
48
|
+
end
|
49
|
+
|
50
|
+
def to(target_method)
|
51
|
+
@target_method = target_method
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def as(method_on_target)
|
56
|
+
@method_on_target = method_on_target
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def with_arguments(*arguments)
|
61
|
+
@delegated_arguments = arguments
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def failure_message
|
66
|
+
base = "Expected #{delegating_method_name} to delegate to #{target_method_name}"
|
67
|
+
add_clarifications_to(base)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def add_clarifications_to(message)
|
73
|
+
if @delegated_arguments.present?
|
74
|
+
message << " with arguments: #{@delegated_arguments.inspect}"
|
75
|
+
end
|
76
|
+
|
77
|
+
if @method_on_target.present?
|
78
|
+
message << " as :#{@method_on_target}"
|
79
|
+
end
|
80
|
+
|
81
|
+
message
|
82
|
+
end
|
83
|
+
|
84
|
+
def delegating_method_name
|
85
|
+
method_name_with_class(@delegating_method)
|
86
|
+
end
|
87
|
+
|
88
|
+
def target_method_name
|
89
|
+
method_name_with_class(@target_method)
|
90
|
+
end
|
91
|
+
|
92
|
+
def method_name_with_class(method)
|
93
|
+
if Class === @subject
|
94
|
+
@subject.name + '.' + method.to_s
|
95
|
+
else
|
96
|
+
@subject.class.name + '#' + method.to_s
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def method_on_target
|
101
|
+
@method_on_target || @delegating_method
|
102
|
+
end
|
103
|
+
|
104
|
+
def ensure_target_method_is_present!
|
105
|
+
if @target_method.blank?
|
106
|
+
raise TargetNotDefinedError
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class DelegateMatcher::TargetNotDefinedError < StandardError
|
112
|
+
def message
|
113
|
+
'Delegation needs a target. Use the #to method to define one, e.g. `post_office.should delegate(:deliver_mail).to(:mailman)`'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class DelegateMatcher::InvalidDelegateMatcher < StandardError
|
118
|
+
def message
|
119
|
+
'#delegate_to does not support #should_not syntax.'
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -1,4 +1,15 @@
|
|
1
1
|
# :enddoc:
|
2
|
+
require 'test/unit/testcase'
|
3
|
+
require 'shoulda/matchers/independent'
|
4
|
+
|
5
|
+
module Test
|
6
|
+
module Unit
|
7
|
+
class TestCase
|
8
|
+
include Shoulda::Matchers::Independent
|
9
|
+
extend Shoulda::Matchers::Independent
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
2
13
|
|
3
14
|
if defined?(ActionController)
|
4
15
|
require 'shoulda/matchers/action_controller'
|
data/shoulda-matchers.gemspec
CHANGED
@@ -18,10 +18,10 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
|
20
20
|
s.add_dependency('activesupport', '>= 3.0.0')
|
21
|
+
s.add_dependency('bourne', '~> 1.1.2')
|
21
22
|
|
22
23
|
s.add_development_dependency('appraisal', '~> 0.4.0')
|
23
24
|
s.add_development_dependency('aruba')
|
24
|
-
s.add_development_dependency('bourne', '~> 1.1.2')
|
25
25
|
s.add_development_dependency('bundler', '~> 1.1')
|
26
26
|
s.add_development_dependency('cucumber', '~> 1.1.9')
|
27
27
|
s.add_development_dependency('rails', '~> 3.0')
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Shoulda::Matchers::ActiveModel::DisallowValueMatcher do
|
4
|
+
it "does not allow any types" do
|
5
|
+
matcher = Shoulda::Matchers::ActiveModel::DisallowValueMatcher.new("abcde")
|
6
|
+
matcher.allowed_types.should == ""
|
7
|
+
end
|
8
|
+
|
9
|
+
context "an attribute with a format validation" do
|
10
|
+
before do
|
11
|
+
define_model :example, :attr => :string do
|
12
|
+
validates_format_of :attr, :with => /abc/
|
13
|
+
end
|
14
|
+
@model = Example.new
|
15
|
+
end
|
16
|
+
|
17
|
+
it "does not match if the value is allowed" do
|
18
|
+
matcher = new_matcher("abcde")
|
19
|
+
matcher.for(:attr)
|
20
|
+
matcher.matches?(@model).should be_false
|
21
|
+
end
|
22
|
+
|
23
|
+
it "matches if the value is not allowed" do
|
24
|
+
matcher = new_matcher("xyz")
|
25
|
+
matcher.for(:attr)
|
26
|
+
matcher.matches?(@model).should be_true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "an attribute with a format validation and a custom message" do
|
31
|
+
before do
|
32
|
+
define_model :example, :attr => :string do
|
33
|
+
validates_format_of :attr, :with => /abc/, :message => 'good message'
|
34
|
+
end
|
35
|
+
@model = Example.new
|
36
|
+
end
|
37
|
+
|
38
|
+
it "does not match if the value and message are both correct" do
|
39
|
+
matcher = new_matcher("abcde")
|
40
|
+
matcher.for(:attr).with_message('good message')
|
41
|
+
matcher.matches?(@model).should be_false
|
42
|
+
end
|
43
|
+
|
44
|
+
it "delegates its failure message to its allow matcher's negative failure message" do
|
45
|
+
allow_matcher = stub_everything(:negative_failure_message => "allow matcher failure")
|
46
|
+
Shoulda::Matchers::ActiveModel::AllowValueMatcher.stubs(:new).returns(allow_matcher)
|
47
|
+
|
48
|
+
matcher = new_matcher("abcde")
|
49
|
+
matcher.for(:attr).with_message('good message')
|
50
|
+
matcher.matches?(@model)
|
51
|
+
|
52
|
+
matcher.failure_message.should == "allow matcher failure"
|
53
|
+
end
|
54
|
+
|
55
|
+
it "matches if the message is correct but the value is not" do
|
56
|
+
matcher = new_matcher("xyz")
|
57
|
+
matcher.for(:attr).with_message('good message')
|
58
|
+
matcher.matches?(@model).should be_true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def new_matcher(value)
|
63
|
+
matcher = Shoulda::Matchers::ActiveModel::DisallowValueMatcher.new(value)
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Shoulda::Matchers::ActiveModel::OnlyIntegerMatcher do
|
4
|
+
context "given an attribute that only allows integer values" do
|
5
|
+
before do
|
6
|
+
define_model :example, :attr => :string do
|
7
|
+
validates_numericality_of :attr, { :only_integer => true }
|
8
|
+
end
|
9
|
+
@model = Example.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it "matches" do
|
13
|
+
matcher = new_matcher(:attr)
|
14
|
+
matcher.matches?(@model).should be_true
|
15
|
+
end
|
16
|
+
|
17
|
+
it "allows integer types" do
|
18
|
+
matcher = new_matcher(:attr)
|
19
|
+
matcher.allowed_types.should == "integer"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns itself when given a message" do
|
23
|
+
matcher = new_matcher(:attr)
|
24
|
+
matcher.with_message("some message").should == matcher
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "given an attribute that only allows integer values with a custom validation message" do
|
29
|
+
before do
|
30
|
+
define_model :example, :attr => :string do
|
31
|
+
validates_numericality_of :attr, { :only_integer => true, :message => 'custom' }
|
32
|
+
end
|
33
|
+
@model = Example.new
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should only allow integer values for that attribute with that message" do
|
37
|
+
matcher = new_matcher(:attr)
|
38
|
+
matcher.with_message(/custom/)
|
39
|
+
matcher.matches?(@model).should be_true
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should not allow integer values for that attribute with another message" do
|
43
|
+
matcher = new_matcher(:attr)
|
44
|
+
matcher.with_message(/wrong/)
|
45
|
+
matcher.matches?(@model).should be_false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "given an attribute that allows values other than integers" do
|
50
|
+
before do
|
51
|
+
@model = define_model(:example, :attr => :string).new
|
52
|
+
end
|
53
|
+
|
54
|
+
it "does not match" do
|
55
|
+
matcher = new_matcher(:attr)
|
56
|
+
matcher.matches?(@model).should be_false
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should fail with the ActiveRecord :not_an_integer message" do
|
60
|
+
matcher = new_matcher(:attr)
|
61
|
+
matcher.matches?(@model)
|
62
|
+
matcher.failure_message.should include 'Expected errors to include "must be an integer"'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def new_matcher(attribute)
|
67
|
+
matcher = Shoulda::Matchers::ActiveModel::OnlyIntegerMatcher.new(attribute)
|
68
|
+
end
|
69
|
+
end
|