shoulda-matchers 2.2.0 → 2.3.0

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.
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 = {})