shoulda-matchers 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/.travis.yml +3 -0
  2. data/Gemfile.lock +1 -1
  3. data/NEWS.md +25 -1
  4. data/README.md +1 -2
  5. data/gemfiles/3.0.gemfile.lock +1 -1
  6. data/gemfiles/3.1.gemfile.lock +1 -1
  7. data/gemfiles/3.2.gemfile.lock +1 -1
  8. data/lib/shoulda/matchers/action_controller.rb +1 -0
  9. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +81 -0
  10. data/lib/shoulda/matchers/active_model/comparison_matcher.rb +1 -1
  11. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +5 -0
  12. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +15 -1
  13. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +1 -1
  14. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +1 -1
  15. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +2 -8
  16. data/lib/shoulda/matchers/active_model/validation_matcher.rb +2 -0
  17. data/lib/shoulda/matchers/active_record.rb +5 -0
  18. data/lib/shoulda/matchers/active_record/association_matcher.rb +90 -113
  19. data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +35 -0
  20. data/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +35 -0
  21. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +37 -0
  22. data/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +35 -0
  23. data/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +57 -0
  24. data/lib/shoulda/matchers/version.rb +1 -1
  25. data/spec/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +63 -0
  26. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +1 -1
  27. data/spec/shoulda/matchers/active_model/comparison_matcher_spec.rb +5 -0
  28. data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +18 -0
  29. data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +10 -0
  30. data/spec/shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb +3 -3
  31. data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +13 -0
  32. data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +14 -0
  33. data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +49 -1
  34. data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +39 -4
  35. metadata +11 -3
@@ -0,0 +1,35 @@
1
+ module Shoulda # :nodoc:
2
+ module Matchers
3
+ module ActiveRecord # :nodoc:
4
+ module AssociationMatchers
5
+ class CounterCacheMatcher
6
+ attr_accessor :missing_option
7
+
8
+ def initialize(counter_cache, name)
9
+ @counter_cache = counter_cache
10
+ @name = name
11
+ @missing_option = ''
12
+ end
13
+
14
+ def description
15
+ "counter_cache => #{counter_cache}"
16
+ end
17
+
18
+ def matches?(subject)
19
+ subject = ModelReflector.new(subject, name)
20
+
21
+ if subject.option_set_properly?(counter_cache, :counter_cache)
22
+ true
23
+ else
24
+ self.missing_option = "#{name} should have #{description}"
25
+ false
26
+ end
27
+ end
28
+
29
+ private
30
+ attr_accessor :counter_cache, :name
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ module Shoulda # :nodoc:
2
+ module Matchers
3
+ module ActiveRecord # :nodoc:
4
+ module AssociationMatchers
5
+ class DependentMatcher
6
+ attr_accessor :missing_option
7
+
8
+ def initialize(dependent, name)
9
+ @dependent = dependent
10
+ @name = name
11
+ @missing_option = ''
12
+ end
13
+
14
+ def description
15
+ "dependent => #{dependent}"
16
+ end
17
+
18
+ def matches?(subject)
19
+ subject = ModelReflector.new(subject, name)
20
+
21
+ if dependent.nil? || subject.option_set_properly?(dependent, :dependent)
22
+ true
23
+ else
24
+ self.missing_option = "#{name} should have #{dependent} dependency"
25
+ false
26
+ end
27
+ end
28
+
29
+ private
30
+ attr_accessor :dependent, :name
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ module Shoulda # :nodoc:
2
+ module Matchers
3
+ module ActiveRecord # :nodoc:
4
+ module AssociationMatchers
5
+ class ModelReflector
6
+ def initialize(subject, name)
7
+ @subject = subject
8
+ @name = name
9
+ end
10
+
11
+ def reflection
12
+ @reflection ||= reflect_on_association(name)
13
+ end
14
+
15
+ def reflect_on_association(name)
16
+ model_class.reflect_on_association(name)
17
+ end
18
+
19
+ def model_class
20
+ subject.class
21
+ end
22
+
23
+ def option_string(key)
24
+ reflection.options[key].to_s
25
+ end
26
+
27
+ def option_set_properly?(option, option_key)
28
+ option.to_s == option_string(option_key)
29
+ end
30
+
31
+ private
32
+ attr_reader :subject, :name
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ module Shoulda # :nodoc:
2
+ module Matchers
3
+ module ActiveRecord # :nodoc:
4
+ module AssociationMatchers
5
+ class OrderMatcher
6
+ attr_accessor :missing_option
7
+
8
+ def initialize(order, name)
9
+ @order = order
10
+ @name = name
11
+ @missing_option = ''
12
+ end
13
+
14
+ def description
15
+ "order => #{order}"
16
+ end
17
+
18
+ def matches?(subject)
19
+ subject = ModelReflector.new(subject, name)
20
+
21
+ if subject.option_set_properly?(order, :order)
22
+ true
23
+ else
24
+ self.missing_option = "#{name} should be ordered by #{order}"
25
+ false
26
+ end
27
+ end
28
+
29
+ private
30
+ attr_accessor :order, :name
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,57 @@
1
+ module Shoulda # :nodoc:
2
+ module Matchers
3
+ module ActiveRecord # :nodoc:
4
+ module AssociationMatchers
5
+ class ThroughMatcher
6
+ attr_accessor :missing_option
7
+
8
+ def initialize(through, name)
9
+ @through = through
10
+ @name = name
11
+ @missing_option = ''
12
+ end
13
+
14
+ def description
15
+ "through #{through}"
16
+ end
17
+
18
+ def matches?(subject)
19
+ self.subject = ModelReflector.new(subject, name)
20
+ through.nil? || association_set_properly?
21
+ end
22
+
23
+ def association_set_properly?
24
+ through_association_exists? && through_association_correct?
25
+ end
26
+
27
+ def through_association_exists?
28
+ if through_reflection.present?
29
+ true
30
+ else
31
+ self.missing_option = "#{name} does not have any relationship to #{through}"
32
+ false
33
+ end
34
+ end
35
+
36
+ def through_reflection
37
+ @through_reflection ||= subject.reflect_on_association(through)
38
+ end
39
+
40
+ def through_association_correct?
41
+ if subject.option_set_properly?(through, :through)
42
+ true
43
+ else
44
+ self.missing_option =
45
+ "Expected #{name} to have #{name} through #{through}, " +
46
+ "but got it through #{subject.option_string(:through)}"
47
+ false
48
+ end
49
+ end
50
+
51
+ private
52
+ attr_accessor :through, :name, :subject
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,5 +1,5 @@
1
1
  module Shoulda
2
2
  module Matchers
3
- VERSION = '2.2.0'.freeze
3
+ VERSION = '2.3.0'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoulda::Matchers::ActionController::RescueFromMatcher do
4
+ context 'a controller that rescues from RuntimeError' do
5
+ it "asserts controller is setup with rescue_from" do
6
+ controller_with_rescue_from.should rescue_from RuntimeError
7
+ end
8
+
9
+ context 'with a handler method' do
10
+ it "asserts rescue_from was set up with handler method" do
11
+ controller_with_rescue_from_and_method.should rescue_from(RuntimeError).with(:error_method)
12
+ end
13
+
14
+ it "asserts rescue_from was not set up with incorrect handler method" do
15
+ controller_with_rescue_from_and_method.should_not rescue_from(RuntimeError).with(:other_method)
16
+ end
17
+
18
+ it "asserts the controller responds to the handler method" do
19
+ matcher = rescue_from(RuntimeError).with(:error_method)
20
+ matcher.matches?(controller_with_rescue_from_and_invalid_method).should be_false
21
+ matcher.failure_message_for_should.should =~ /does not respond to/
22
+ end
23
+ end
24
+
25
+ context 'without a handler method' do
26
+ it "the handler method is not included in the description" do
27
+ matcher = rescue_from(RuntimeError)
28
+ matcher.matches?(controller_with_rescue_from).should be_true
29
+ matcher.description.should_not =~ /with #/
30
+ end
31
+ end
32
+ end
33
+
34
+ context 'a controller that does not rescue from RuntimeError' do
35
+ it "asserts controller is not setup with rescue_from" do
36
+ matcher = rescue_from RuntimeError
37
+ define_controller("RandomController").should_not matcher
38
+ matcher.failure_message_for_should_not.should =~ /Did not expect \w+ to rescue from/
39
+ end
40
+ end
41
+
42
+ def controller_with_rescue_from
43
+ define_controller "RescueRuntimeError" do
44
+ rescue_from(RuntimeError) {}
45
+ end
46
+ end
47
+
48
+ def controller_with_rescue_from_and_invalid_method
49
+ define_controller "RescueRuntimeErrorWithMethod" do
50
+ rescue_from RuntimeError, with: :error_method
51
+ end
52
+ end
53
+
54
+ def controller_with_rescue_from_and_method
55
+ controller = controller_with_rescue_from_and_invalid_method
56
+ class << controller
57
+ def error_method
58
+ true
59
+ end
60
+ end
61
+ controller
62
+ end
63
+ end
@@ -110,7 +110,7 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher do
110
110
  matcher = described_class.new('foo').for(:attr)
111
111
  matcher.description
112
112
 
113
- expect { matcher.matches?(model) }.not_to raise_error(NoMethodError)
113
+ expect { matcher.matches?(model) }.not_to raise_error
114
114
  end
115
115
  end
116
116
 
@@ -21,6 +21,11 @@ describe Shoulda::Matchers::ActiveModel::ComparisonMatcher do
21
21
  it { instance_without_validations.should_not matcher.is_less_than_or_equal_to(2) }
22
22
  end
23
23
 
24
+ context 'is_equal_to' do
25
+ it { instance_with_validations(:equal_to => 0).should matcher.is_equal_to(0) }
26
+ it { instance_without_validations.should_not matcher.is_equal_to(0) }
27
+ end
28
+
24
29
  def instance_with_validations(options = {})
25
30
  define_model :example, :attr => :string do
26
31
  validates_numericality_of :attr, options
@@ -15,6 +15,24 @@ describe Shoulda::Matchers::ActiveModel::DisallowValueMatcher do
15
15
  end
16
16
  end
17
17
 
18
+ context "an attribute with a context-dependent validation" do
19
+ context "without the validation context" do
20
+ it "does not match" do
21
+ validating_format(:with => /abc/, :on => :customisable).should_not matcher("xyz").for(:attr)
22
+ end
23
+ end
24
+
25
+ context "with the validation context" do
26
+ it "disallows a bad value" do
27
+ validating_format(:with => /abc/, :on => :customisable).should matcher("xyz").for(:attr).on(:customisable)
28
+ end
29
+
30
+ it "does not match a good value" do
31
+ validating_format(:with => /abc/, :on => :customisable).should_not matcher("abcde").for(:attr).on(:customisable)
32
+ end
33
+ end
34
+ end
35
+
18
36
  context 'an attribute with a format validation and a custom message' do
19
37
  it 'does not match if the value and message are both correct' do
20
38
  validating_format(:with => /abc/, :message => 'good message').
@@ -8,6 +8,16 @@ describe Shoulda::Matchers::ActiveModel::EnsureInclusionOfMatcher do
8
8
  end
9
9
  end
10
10
 
11
+ context 'with an integer column' do
12
+ it 'can verify a zero in the array' do
13
+ model = define_model(:example, :attr => :integer) do
14
+ validates_inclusion_of :attr, :in => [0, 1, 2]
15
+ end.new
16
+
17
+ model.should ensure_inclusion_of(:attr).in_array([0,1,2])
18
+ end
19
+ end
20
+
11
21
  context 'with true/false values' do
12
22
  it 'can verify outside values to ensure the negative case' do
13
23
  define_model(:example, :attr => :string).new.
@@ -118,7 +118,7 @@ describe Shoulda::Matchers::ActiveModel::EnsureLengthOfMatcher do
118
118
  it "does not raise an exception" do
119
119
  expect {
120
120
  validating_length(:maximum => 4).should ensure_length_of(:attr).is_at_most(4)
121
- }.to_not raise_exception(I18n::MissingInterpolationArgument)
121
+ }.to_not raise_exception
122
122
  end
123
123
  end
124
124
 
@@ -132,7 +132,7 @@ describe Shoulda::Matchers::ActiveModel::EnsureLengthOfMatcher do
132
132
  it "does not raise an exception" do
133
133
  expect {
134
134
  validating_length(:minimum => 4).should ensure_length_of(:attr).is_at_least(4)
135
- }.to_not raise_exception(I18n::MissingInterpolationArgument)
135
+ }.to_not raise_exception
136
136
  end
137
137
  end
138
138
 
@@ -146,7 +146,7 @@ describe Shoulda::Matchers::ActiveModel::EnsureLengthOfMatcher do
146
146
  it "does not raise an exception" do
147
147
  expect {
148
148
  validating_length(:is => 4).should ensure_length_of(:attr).is_equal_to(4)
149
- }.to_not raise_exception(I18n::MissingInterpolationArgument)
149
+ }.to_not raise_exception
150
150
  end
151
151
  end
152
152
  end
@@ -121,6 +121,19 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
121
121
  end
122
122
  end
123
123
 
124
+ context 'when the subject is stubbed' do
125
+ it 'retains stubs on submatchers' do
126
+ subject = define_model :example, :attr => :string do
127
+ validates_numericality_of :attr, :odd => true
128
+ before_validation :set_attr!
129
+ def set_attr!; self.attr = 5 end
130
+ end.new
131
+
132
+ subject.stubs(:set_attr!)
133
+ subject.should matcher.odd
134
+ end
135
+ end
136
+
124
137
  def validating_numericality(options = {})
125
138
  define_model :example, :attr => :string do
126
139
  validates_numericality_of :attr, options
@@ -120,6 +120,20 @@ describe Shoulda::Matchers::ActiveModel::ValidatePresenceOfMatcher do
120
120
  end
121
121
  end
122
122
 
123
+ context "an attribute with a context-dependent validation" do
124
+ context "without the validation context" do
125
+ it "does not match" do
126
+ validating_presence(:on => :customisable).should_not matcher
127
+ end
128
+ end
129
+
130
+ context "with the validation context" do
131
+ it "matches" do
132
+ validating_presence(:on => :customisable).should matcher.on(:customisable)
133
+ end
134
+ end
135
+ end
136
+
123
137
  def matcher
124
138
  validate_presence_of(:attr)
125
139
  end
@@ -131,6 +131,13 @@ describe Shoulda::Matchers::ActiveModel::ValidateUniquenessOfMatcher do
131
131
  should_not matcher.scoped_to(:scope1, :scope2, :other)
132
132
  end
133
133
  end
134
+
135
+ context 'when too broad of a scope is specified' do
136
+ it 'rejects' do
137
+ validating_scoped_uniqueness([:scope1, :scope2], :date, :scope1 => Date.today, :scope2 => Date.today).
138
+ should_not matcher.scoped_to(:scope1)
139
+ end
140
+ end
134
141
  end
135
142
 
136
143
  context 'when the scoped attribute is a datetime' do
@@ -160,12 +167,53 @@ describe Shoulda::Matchers::ActiveModel::ValidateUniquenessOfMatcher do
160
167
  end
161
168
  end
162
169
 
170
+ context 'when too broad of a scope is specified' do
171
+ it 'rejects' do
172
+ validating_scoped_uniqueness([:scope1, :scope2], :datetime, :scope1 => DateTime.now, :scope2 => DateTime.now).
173
+ should_not matcher.scoped_to(:scope1)
174
+ end
175
+ end
176
+ end
177
+
178
+ context 'when the scoped attribute is a uuid' do
179
+ it 'accepts' do
180
+ validating_scoped_uniqueness([:scope1], :uuid, :scope1 => SecureRandom.uuid).
181
+ should matcher.scoped_to(:scope1)
182
+ end
183
+
163
184
  context 'with an existing record that conflicts with scope.next' do
164
185
  it 'accepts' do
165
- validating_scoped_uniqueness_with_conflicting_next(:scope1, :scope1 => 1).
186
+ validating_scoped_uniqueness_with_conflicting_next(:scope1, :uuid, :scope1 => SecureRandom.uuid).
166
187
  should matcher.scoped_to(:scope1)
167
188
  end
168
189
  end
190
+
191
+ context 'with a nil value' do
192
+ it 'accepts' do
193
+ validating_scoped_uniqueness([:scope1], :uuid, :scope1 => nil).
194
+ should matcher.scoped_to(:scope1)
195
+ end
196
+ end
197
+
198
+ context 'when too narrow of a scope is specified' do
199
+ it 'rejects' do
200
+ record = validating_scoped_uniqueness([:scope1, :scope2], :uuid,
201
+ :scope1 => SecureRandom.uuid,
202
+ :scope2 => SecureRandom.uuid
203
+ )
204
+ record.should_not matcher.scoped_to(:scope1, :scope2, :other)
205
+ end
206
+ end
207
+
208
+ context 'when too broad of a scope is specified' do
209
+ it 'rejects' do
210
+ record = validating_scoped_uniqueness([:scope1, :scope2], :uuid,
211
+ :scope1 => SecureRandom.uuid,
212
+ :scope2 => SecureRandom.uuid
213
+ )
214
+ record.should_not matcher.scoped_to(:scope1)
215
+ end
216
+ end
169
217
  end
170
218
 
171
219
  def create_existing_record(attributes = {})