shoulda-matchers 3.0.0.rc1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -3
  3. data/Gemfile.lock +12 -41
  4. data/NEWS.md +118 -26
  5. data/README.md +34 -11
  6. data/doc_config/yard/templates/default/fulldoc/html/css/bootstrap.css +0 -0
  7. data/doc_config/yard/templates/default/fulldoc/html/css/style.css +4 -0
  8. data/gemfiles/4.0.0.gemfile +2 -3
  9. data/gemfiles/4.0.0.gemfile.lock +47 -77
  10. data/gemfiles/4.0.1.gemfile +2 -3
  11. data/gemfiles/4.0.1.gemfile.lock +51 -79
  12. data/gemfiles/4.1.gemfile +2 -3
  13. data/gemfiles/4.1.gemfile.lock +73 -103
  14. data/gemfiles/4.2.gemfile +2 -3
  15. data/gemfiles/4.2.gemfile.lock +90 -124
  16. data/lib/shoulda/matchers.rb +1 -0
  17. data/lib/shoulda/matchers/action_controller/callback_matcher.rb +6 -8
  18. data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +1 -3
  19. data/lib/shoulda/matchers/action_controller/flash_store.rb +1 -8
  20. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +140 -88
  21. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +2 -5
  22. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +5 -10
  23. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +2 -4
  24. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +1 -3
  25. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +3 -5
  26. data/lib/shoulda/matchers/action_controller/route_matcher.rb +5 -7
  27. data/lib/shoulda/matchers/action_controller/set_flash_matcher.rb +35 -9
  28. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +3 -3
  29. data/lib/shoulda/matchers/active_model.rb +57 -1
  30. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +2 -5
  31. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +162 -54
  32. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +5 -2
  33. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +1 -3
  34. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +24 -11
  35. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +4 -3
  36. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +0 -2
  37. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +4 -3
  38. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +2 -1
  39. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +15 -13
  40. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +3 -3
  41. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +3 -3
  42. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +4 -4
  43. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +8 -8
  44. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +8 -8
  45. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +12 -14
  46. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +10 -4
  47. data/lib/shoulda/matchers/active_model/validation_matcher.rb +0 -3
  48. data/lib/shoulda/matchers/active_model/validator.rb +0 -8
  49. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +4 -6
  50. data/lib/shoulda/matchers/active_record/association_matcher.rb +58 -43
  51. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +2 -2
  52. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +3 -5
  53. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +3 -5
  54. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +1 -4
  55. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +3 -5
  56. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +7 -7
  57. data/lib/shoulda/matchers/doublespeak/double.rb +10 -1
  58. data/lib/shoulda/matchers/doublespeak/double_collection.rb +13 -5
  59. data/lib/shoulda/matchers/doublespeak/method_call.rb +10 -1
  60. data/lib/shoulda/matchers/doublespeak/object_double.rb +2 -1
  61. data/lib/shoulda/matchers/doublespeak/world.rb +10 -0
  62. data/lib/shoulda/matchers/error.rb +4 -0
  63. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +11 -10
  64. data/lib/shoulda/matchers/integrations/libraries.rb +1 -0
  65. data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +1 -1
  66. data/lib/shoulda/matchers/integrations/libraries/active_model.rb +1 -1
  67. data/lib/shoulda/matchers/integrations/libraries/active_record.rb +1 -1
  68. data/lib/shoulda/matchers/integrations/libraries/rails.rb +2 -1
  69. data/lib/shoulda/matchers/integrations/libraries/routing.rb +27 -0
  70. data/lib/shoulda/matchers/integrations/test_frameworks/active_support_test_case.rb +1 -1
  71. data/lib/shoulda/matchers/integrations/test_frameworks/minitest_4.rb +1 -1
  72. data/lib/shoulda/matchers/integrations/test_frameworks/minitest_5.rb +1 -1
  73. data/lib/shoulda/matchers/integrations/test_frameworks/missing_test_framework.rb +1 -1
  74. data/lib/shoulda/matchers/integrations/test_frameworks/rspec.rb +2 -2
  75. data/lib/shoulda/matchers/integrations/test_frameworks/test_unit.rb +1 -1
  76. data/lib/shoulda/matchers/routing.rb +10 -0
  77. data/lib/shoulda/matchers/version.rb +1 -1
  78. data/script/SUPPORTED_VERSIONS +1 -1
  79. data/spec/acceptance/independent_matchers_spec.rb +103 -42
  80. data/spec/doublespeak_spec_helper.rb +5 -1
  81. data/spec/support/acceptance/adds_shoulda_matchers_to_project.rb +34 -11
  82. data/spec/support/acceptance/helpers/rspec_helpers.rb +9 -13
  83. data/spec/support/acceptance/helpers/step_helpers.rb +13 -0
  84. data/spec/support/acceptance/matchers/have_output.rb +1 -1
  85. data/spec/support/acceptance/matchers/indicate_number_of_tests_was_run_matcher.rb +1 -1
  86. data/spec/support/tests/command_runner.rb +5 -1
  87. data/spec/support/unit/helpers/active_record_versions.rb +0 -4
  88. data/spec/support/unit/shared_examples/set_session_or_flash.rb +8 -3
  89. data/spec/unit/shoulda/matchers/action_controller/permit_matcher_spec.rb +198 -39
  90. data/spec/unit/shoulda/matchers/action_controller/route_matcher_spec.rb +269 -102
  91. data/spec/unit/shoulda/matchers/action_controller/set_flash_matcher_spec.rb +24 -0
  92. data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +118 -101
  93. data/spec/unit/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +0 -82
  94. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +148 -121
  95. data/spec/unit/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +20 -8
  96. data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +64 -183
  97. data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +14 -0
  98. data/spec/unit/shoulda/matchers/doublespeak/double_collection_spec.rb +60 -0
  99. data/spec/unit/shoulda/matchers/doublespeak/double_spec.rb +23 -7
  100. data/spec/unit/shoulda/matchers/routing/route_matcher_spec.rb +242 -0
  101. data/spec/unit_spec_helper.rb +4 -0
  102. data/tasks/documentation.rb +35 -0
  103. metadata +9 -8
  104. data/Guardfile +0 -5
  105. data/cucumber.yml +0 -1
  106. data/lib/shoulda/matchers/active_model/validator_with_captured_range_error.rb +0 -12
@@ -3,7 +3,7 @@ module Shoulda
3
3
  module ActionController
4
4
  # The `redirect_to` matcher tests that an action redirects to a certain
5
5
  # location. In a test suite using RSpec, it is very similar to
6
- # rspec-rails's `redirect_to` matcher. In a test suite using Test::Unit /
6
+ # rspec-rails's `redirect_to` matcher. In a test suite using Minitest +
7
7
  # Shoulda, it provides a more expressive syntax over
8
8
  # `assert_redirected_to`.
9
9
  #
@@ -23,7 +23,7 @@ module Shoulda
23
23
  # end
24
24
  # end
25
25
  #
26
- # # Test::Unit
26
+ # # Minitest (Shoulda)
27
27
  # class PostsControllerTest < ActionController::TestCase
28
28
  # context 'GET #show' do
29
29
  # setup { get :show }
@@ -43,9 +43,6 @@ module Shoulda
43
43
  class RedirectToMatcher
44
44
  attr_reader :failure_message, :failure_message_when_negated
45
45
 
46
- alias failure_message_for_should failure_message
47
- alias failure_message_for_should_not failure_message_when_negated
48
-
49
46
  def initialize(url_or_description, context, &block)
50
47
  @url_block = nil
51
48
  @url = nil
@@ -3,8 +3,8 @@ module Shoulda
3
3
  module ActionController
4
4
  # The `render_template` matcher tests that an action renders a template
5
5
  # or partial. In RSpec, it is very similar to rspec-rails's
6
- # `render_template` matcher. In Test::Unit, it provides a more expressive
7
- # syntax over `assert_template`.
6
+ # `render_template` matcher. In a test suite using Minitest + Shoulda, it
7
+ # provides a more expressive syntax over `assert_template`.
8
8
  #
9
9
  # class PostsController < ApplicationController
10
10
  # def show
@@ -20,22 +20,20 @@ module Shoulda
20
20
  # before { get :show }
21
21
  #
22
22
  # it { should render_template('show') }
23
- # it { should render_template(partial: 'sidebar') }
23
+ # it { should render_template(partial: '_sidebar') }
24
24
  # end
25
25
  # end
26
26
  #
27
- # # Test::Unit
27
+ # # Minitest (Shoulda)
28
28
  # class PostsControllerTest < ActionController::TestCase
29
29
  # context 'GET #show' do
30
30
  # setup { get :show }
31
31
  #
32
32
  # should render_template('show')
33
- # should render_template(partial: 'sidebar')
33
+ # should render_template(partial: '_sidebar')
34
34
  # end
35
35
  # end
36
36
  #
37
- #
38
- #
39
37
  # @return [RenderTemplateMatcher]
40
38
  #
41
39
  def render_template(options = {}, message = nil)
@@ -46,9 +44,6 @@ module Shoulda
46
44
  class RenderTemplateMatcher
47
45
  attr_reader :failure_message, :failure_message_when_negated
48
46
 
49
- alias failure_message_for_should failure_message
50
- alias failure_message_for_should_not failure_message_when_negated
51
-
52
47
  def initialize(options, message, context)
53
48
  @options = options
54
49
  @message = message
@@ -19,7 +19,7 @@ module Shoulda
19
19
  # end
20
20
  # end
21
21
  #
22
- # # Test::Unit
22
+ # # Minitest (Shoulda)
23
23
  # class PostsControllerTest < ActionController::TestCase
24
24
  # context 'GET #show' do
25
25
  # setup { get :show }
@@ -46,7 +46,7 @@ module Shoulda
46
46
  # end
47
47
  # end
48
48
  #
49
- # # Test::Unit
49
+ # # Minitest (Shoulda)
50
50
  # class PostsControllerTest < ActionController::TestCase
51
51
  # context 'GET #sidebar' do
52
52
  # setup { get :sidebar }
@@ -88,12 +88,10 @@ module Shoulda
88
88
  def failure_message
89
89
  "Expected #{expectation}, but #{result}"
90
90
  end
91
- alias failure_message_for_should failure_message
92
91
 
93
92
  def failure_message_when_negated
94
93
  "Did not expect #{expectation}, but #{result}"
95
94
  end
96
- alias failure_message_for_should_not failure_message_when_negated
97
95
 
98
96
  def description
99
97
  description = 'render with '
@@ -23,7 +23,7 @@ module Shoulda
23
23
  # end
24
24
  # end
25
25
  #
26
- # # Test::Unit
26
+ # # Minitest (Shoulda)
27
27
  # class ApplicationControllerTest < ActionController::TestCase
28
28
  # should rescue_from(ActiveRecord::RecordNotFound).
29
29
  # with(:handle_not_found)
@@ -62,12 +62,10 @@ module Shoulda
62
62
  def failure_message
63
63
  "Expected #{expectation}"
64
64
  end
65
- alias failure_message_for_should failure_message
66
65
 
67
66
  def failure_message_when_negated
68
67
  "Did not expect #{expectation}"
69
68
  end
70
- alias failure_message_for_should_not failure_message_when_negated
71
69
 
72
70
  protected
73
71
 
@@ -21,7 +21,7 @@ module Shoulda
21
21
  # end
22
22
  # end
23
23
  #
24
- # # Test::Unit
24
+ # # Minitest (Shoulda)
25
25
  # class PostsControllerTest < ActionController::TestCase
26
26
  # context 'GET #index' do
27
27
  # setup { get :index }
@@ -47,7 +47,7 @@ module Shoulda
47
47
  # end
48
48
  # end
49
49
  #
50
- # # Test::Unit
50
+ # # Minitest (Shoulda)
51
51
  # class PostsControllerTest < ActionController::TestCase
52
52
  # context 'DELETE #destroy' do
53
53
  # setup { delete :destroy }
@@ -73,7 +73,7 @@ module Shoulda
73
73
  # end
74
74
  # end
75
75
  #
76
- # # Test::Unit
76
+ # # Minitest (Shoulda)
77
77
  # class PostsControllerTest < ActionController::TestCase
78
78
  # context 'GET #show' do
79
79
  # setup { get :show }
@@ -102,12 +102,10 @@ module Shoulda
102
102
  def failure_message
103
103
  "Expected #{expectation}"
104
104
  end
105
- alias failure_message_for_should failure_message
106
105
 
107
106
  def failure_message_when_negated
108
107
  "Did not expect #{expectation}"
109
108
  end
110
- alias failure_message_for_should_not failure_message_when_negated
111
109
 
112
110
  def description
113
111
  "respond with #{@status}"
@@ -4,8 +4,9 @@ module Shoulda
4
4
  # The `route` matcher tests that a route resolves to a controller,
5
5
  # action, and params; and that the controller, action, and params
6
6
  # generates the same route. For an RSpec suite, this is like using a
7
- # combination of `route_to` and `be_routable`. For a Test::Unit suite, it
8
- # provides a more expressive syntax over `assert_routing`.
7
+ # combination of `route_to` and `be_routable`. In a test suite using
8
+ # Minitest + Shoulda, it provides a more expressive syntax over
9
+ # `assert_routing`.
9
10
  #
10
11
  # You can use this matcher either in a controller test case or in a
11
12
  # routing test case. For instance, given these routes:
@@ -28,7 +29,7 @@ module Shoulda
28
29
  # it { should route(:get, '/posts/1').to(action: :show, id: 1) }
29
30
  # end
30
31
  #
31
- # # Test::Unit
32
+ # # Minitest (Shoulda)
32
33
  # class PostsControllerTest < ActionController::TestCase
33
34
  # should route(:get, '/posts').to(action: 'index')
34
35
  # should route(:get, '/posts/1').to(action: :show, id: 1)
@@ -49,7 +50,7 @@ module Shoulda
49
50
  # end
50
51
  # end
51
52
  #
52
- # # Test::Unit
53
+ # # Minitest (Shoulda)
53
54
  # class RoutesTest < ActionController::IntegrationTest
54
55
  # should route(:get, '/posts').
55
56
  # to(controller: :posts, action: :index)
@@ -99,9 +100,6 @@ module Shoulda
99
100
 
100
101
  attr_reader :failure_message, :failure_message_when_negated
101
102
 
102
- alias failure_message_for_should failure_message
103
- alias failure_message_for_should_not failure_message_when_negated
104
-
105
103
  def to(*args)
106
104
  @params = RouteParams.new(args).normalize
107
105
  self
@@ -30,7 +30,7 @@ module Shoulda
30
30
  # end
31
31
  # end
32
32
  #
33
- # # Test::Unit
33
+ # # Minitest (Shoulda)
34
34
  # class PostsControllerTest < ActionController::TestCase
35
35
  # context 'GET #index' do
36
36
  # setup { get :index }
@@ -67,7 +67,7 @@ module Shoulda
67
67
  # end
68
68
  # end
69
69
  #
70
- # # Test::Unit
70
+ # # Minitest (Shoulda)
71
71
  # class PostsControllerTest < ActionController::TestCase
72
72
  # context 'GET #index' do
73
73
  # setup { get :show }
@@ -100,7 +100,7 @@ module Shoulda
100
100
  # end
101
101
  # end
102
102
  #
103
- # # Test::Unit
103
+ # # Minitest (Shoulda)
104
104
  # class PostsControllerTest < ActionController::TestCase
105
105
  # context 'GET #index' do
106
106
  # setup { get :show }
@@ -129,19 +129,19 @@ module Shoulda
129
129
  # before { get :show }
130
130
  #
131
131
  # it { should set_flash.now }
132
- # it { should set_flash[:foo].now }
133
- # it { should set_flash[:foo].to('bar').now }
132
+ # it { should set_flash.now[:foo] }
133
+ # it { should set_flash.now[:foo].to('bar') }
134
134
  # end
135
135
  # end
136
136
  #
137
- # # Test::Unit
137
+ # # Minitest (Shoulda)
138
138
  # class PostsControllerTest < ActionController::TestCase
139
139
  # context 'GET #index' do
140
140
  # setup { get :show }
141
141
  #
142
142
  # should set_flash.now
143
- # should set_flash[:foo].now
144
- # should set_flash[:foo].to('bar').now
143
+ # should set_flash.now[:foo]
144
+ # should set_flash.now[:foo].to('bar')
145
145
  # end
146
146
  # end
147
147
  #
@@ -173,6 +173,10 @@ module Shoulda
173
173
  end
174
174
 
175
175
  def now
176
+ if key || expected_value
177
+ raise QualifierOrderError
178
+ end
179
+
176
180
  store = FlashStore.now
177
181
  @underlying_matcher = SetSessionOrFlashMatcher.new(store)
178
182
  self
@@ -184,18 +188,40 @@ module Shoulda
184
188
  end
185
189
 
186
190
  def [](key)
191
+ @key = key
187
192
  underlying_matcher[key]
188
193
  self
189
194
  end
190
195
 
191
196
  def to(expected_value = nil, &block)
197
+ @expected_value = expected_value
192
198
  underlying_matcher.to(expected_value, &block)
193
199
  self
194
200
  end
195
201
 
196
202
  protected
197
203
 
198
- attr_reader :underlying_matcher
204
+ attr_reader :underlying_matcher, :key, :expected_value
205
+
206
+ # @private
207
+ class QualifierOrderError < StandardError
208
+ def message
209
+ <<-MESSAGE.strip
210
+ Using `set_flash` with the `now` qualifier and specifying `now` after other
211
+ qualifiers is no longer allowed.
212
+
213
+ You'll want to use `now` immediately after `set_flash`. For instance:
214
+
215
+ # Valid
216
+ should set_flash.now[:foo]
217
+ should set_flash.now[:foo].to('bar')
218
+
219
+ # Invalid
220
+ should set_flash[:foo].now
221
+ should set_flash[:foo].to('bar').now
222
+ MESSAGE
223
+ end
224
+ end
199
225
  end
200
226
  end
201
227
  end
@@ -30,7 +30,7 @@ module Shoulda
30
30
  # end
31
31
  # end
32
32
  #
33
- # # Test::Unit
33
+ # # Minitest (Shoulda)
34
34
  # class PostsControllerTest < ActionController::TestCase
35
35
  # context 'GET #index' do
36
36
  # setup { get :index }
@@ -67,7 +67,7 @@ module Shoulda
67
67
  # end
68
68
  # end
69
69
  #
70
- # # Test::Unit
70
+ # # Minitest (Shoulda)
71
71
  # class PostsControllerTest < ActionController::TestCase
72
72
  # context 'GET #index' do
73
73
  # setup { get :show }
@@ -100,7 +100,7 @@ module Shoulda
100
100
  # end
101
101
  # end
102
102
  #
103
- # # Test::Unit
103
+ # # Minitest (Shoulda)
104
104
  # class PostsControllerTest < ActionController::TestCase
105
105
  # context 'GET #index' do
106
106
  # setup { get :show }
@@ -2,7 +2,6 @@ require 'shoulda/matchers/active_model/helpers'
2
2
  require 'shoulda/matchers/active_model/validation_matcher'
3
3
  require 'shoulda/matchers/active_model/validator'
4
4
  require 'shoulda/matchers/active_model/strict_validator'
5
- require 'shoulda/matchers/active_model/validator_with_captured_range_error'
6
5
  require 'shoulda/matchers/active_model/allow_value_matcher'
7
6
  require 'shoulda/matchers/active_model/disallow_value_matcher'
8
7
  require 'shoulda/matchers/active_model/validate_length_of_matcher'
@@ -24,6 +23,63 @@ require 'shoulda/matchers/active_model/have_secure_password_matcher'
24
23
 
25
24
  module Shoulda
26
25
  module Matchers
26
+ # This mixin provides matchers that are used to test behavior, such as
27
+ # validations, that you've added to your ActiveModel (or ActiveRecord)
28
+ # objects.
29
+ #
30
+ # ### Testing conditional validations
31
+ #
32
+ # If your model defines a validation conditionally -- meaning that the
33
+ # validation is declared with an `:if` or `:unless` option -- how do you
34
+ # test it? You might expect the validation matchers here to have
35
+ # corresponding `if` or `unless` qualifiers, but this isn't what you use.
36
+ # Instead, before using the matcher in question, you place the record
37
+ # you're testing in a state such that the validation you're also testing
38
+ # will be run. A common way to do this is to make a new `context` and
39
+ # override the subject to populate the record accordingly. You'll also want
40
+ # to make sure to test that the validation is *not* run when the
41
+ # conditional fails.
42
+ #
43
+ # Here's an example to illustrate what we mean:
44
+ #
45
+ # class User
46
+ # include ActiveModel::Model
47
+ #
48
+ # attr_accessor :role, :admin
49
+ #
50
+ # validates_presence_of :role, if: :admin
51
+ # end
52
+ #
53
+ # # RSpec
54
+ # describe User do
55
+ # context "when an admin" do
56
+ # subject { User.new(admin: true) }
57
+ #
58
+ # it { should validate_presence_of(:role) }
59
+ # end
60
+ #
61
+ # context "when not an admin" do
62
+ # subject { User.new(admin: false) }
63
+ #
64
+ # it { should_not validate_presence_of(:role) }
65
+ # end
66
+ # end
67
+ #
68
+ # # Minitest (Shoulda)
69
+ # class UserTest < ActiveSupport::TestCase
70
+ # context "when an admin" do
71
+ # subject { User.new(admin: true) }
72
+ #
73
+ # should validate_presence_of(:role)
74
+ # end
75
+ #
76
+ # context "when not an admin" do
77
+ # subject { User.new(admin: false) }
78
+ #
79
+ # should_not validate_presence_of(:role)
80
+ # end
81
+ # end
82
+ #
27
83
  module ActiveModel
28
84
  end
29
85
  end
@@ -31,7 +31,7 @@ module Shoulda
31
31
  # it { should_not allow_mass_assignment_of(:encrypted_password) }
32
32
  # end
33
33
  #
34
- # # Test::Unit
34
+ # # Minitest (Shoulda)
35
35
  # class PostTest < ActiveSupport::TestCase
36
36
  # should allow_mass_assignment_of(:title)
37
37
  # end
@@ -60,7 +60,7 @@ module Shoulda
60
60
  # it { should allow_mass_assignment_of(:title).as(:admin) }
61
61
  # end
62
62
  #
63
- # # Test::Unit
63
+ # # Minitest (Shoulda)
64
64
  # class PostTest < ActiveSupport::TestCase
65
65
  # should allow_mass_assignment_of(:title).as(:admin)
66
66
  # end
@@ -75,9 +75,6 @@ module Shoulda
75
75
  class AllowMassAssignmentOfMatcher
76
76
  attr_reader :failure_message, :failure_message_when_negated
77
77
 
78
- alias failure_message_for_should failure_message
79
- alias failure_message_for_should_not failure_message_when_negated
80
-
81
78
  def initialize(attribute)
82
79
  @attribute = attribute.to_s
83
80
  @options = {}
@@ -1,15 +1,11 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  module ActiveModel
4
- # The `allow_value` matcher is used to test that an attribute of a model
5
- # can or cannot be set to a particular value or values. It is most
6
- # commonly used in conjunction with the `validates_format_of` validation.
4
+ # The `allow_value` matcher (or its alias, `allow_values`) is used to
5
+ # ensure that an attribute is valid or invalid if set to one or more
6
+ # values.
7
7
  #
8
- # #### should
9
- #
10
- # In the positive form, `allow_value` asserts that an attribute can be
11
- # set to one or more values, succeeding if none of the values cause the
12
- # record to be invalid:
8
+ # Take this model for example:
13
9
  #
14
10
  # class UserProfile
15
11
  # include ActiveModel::Model
@@ -18,45 +14,136 @@ module Shoulda
18
14
  # validates_format_of :website_url, with: URI.regexp
19
15
  # end
20
16
  #
17
+ # You can use `allow_value` to test one value at a time:
18
+ #
19
+ # # RSpec
20
+ # describe UserProfile do
21
+ # it { should allow_value('http://foo.com').for(:website_url) }
22
+ # it { should allow_value('http://bar.com').for(:website_url) }
23
+ # end
24
+ #
25
+ # # Minitest (Shoulda)
26
+ # class UserProfileTest < ActiveSupport::TestCase
27
+ # should allow_value('http://foo.com').for(:website_url)
28
+ # should allow_value('http://bar.com').for(:website_url)
29
+ # end
30
+ #
31
+ # You can also test multiple values in one go, if you like. In the
32
+ # positive sense, this makes an assertion that none of the values cause the
33
+ # record to be invalid. In the negative sense, this makes an assertion
34
+ # that none of the values cause the record to be valid:
35
+ #
21
36
  # # RSpec
22
37
  # describe UserProfile do
23
38
  # it do
24
- # should allow_value('http://foo.com', 'http://bar.com/baz').
39
+ # should allow_values('http://foo.com', 'http://bar.com').
40
+ # for(:website_url)
41
+ # end
42
+ #
43
+ # it do
44
+ # should_not allow_values('http://foo.com', 'buz').
25
45
  # for(:website_url)
26
46
  # end
27
47
  # end
28
48
  #
29
- # # Test::Unit
49
+ # # Minitest (Shoulda)
30
50
  # class UserProfileTest < ActiveSupport::TestCase
31
- # should allow_value('http://foo.com', 'http://bar.com/baz').
51
+ # should allow_values('http://foo.com', 'http://bar.com/baz').
52
+ # for(:website_url)
53
+ #
54
+ # should_not allow_values('http://foo.com', 'buz').
32
55
  # for(:website_url)
33
56
  # end
34
57
  #
35
- # #### should_not
58
+ # #### Caveats
36
59
  #
37
- # In the negative form, `allow_value` asserts that an attribute cannot be
38
- # set to one or more values, succeeding if the *first* value causes the
39
- # record to be invalid.
60
+ # When using `allow_value` or any matchers that depend on it, you may
61
+ # encounter a CouldNotSetAttributeError. This exception is raised if the
62
+ # matcher, in attempting to set a value on the attribute, detects that
63
+ # the value set is different from the value that the attribute returns
64
+ # upon reading it back.
40
65
  #
41
- # **This can be surprising** so in this case if you need to check that
42
- # *all* of the values are invalid, use separate assertions:
66
+ # This usually happens if the writer method (`foo=`, `bar=`, etc.) for
67
+ # that attribute has custom logic to ignore certain incoming values or
68
+ # change them in any way. Here are three examples we've seen:
43
69
  #
44
- # class UserProfile
45
- # include ActiveModel::Model
46
- # attr_accessor :website_url
70
+ # * You're attempting to assert that an attribute should not allow nil,
71
+ # yet the attribute's writer method contains a conditional to do nothing
72
+ # if the attribute is set to nil:
47
73
  #
48
- # validates_format_of :website_url, with: URI.regexp
49
- # end
74
+ # class Foo
75
+ # include ActiveModel::Model
50
76
  #
51
- # describe UserProfile do
52
- # # One assertion: 'buz' and 'bar' will not be tested
53
- # it { should_not allow_value('fiz', 'buz', 'bar').for(:website_url) }
77
+ # attr_reader :bar
54
78
  #
55
- # # Three assertions, all tested separately
56
- # it { should_not allow_value('fiz').for(:website_url) }
57
- # it { should_not allow_value('buz').for(:website_url) }
58
- # it { should_not allow_value('bar').for(:website_url) }
59
- # end
79
+ # def bar=(value)
80
+ # return if value.nil?
81
+ # @bar = value
82
+ # end
83
+ # end
84
+ #
85
+ # describe Foo do
86
+ # it do
87
+ # foo = Foo.new
88
+ # foo.bar = "baz"
89
+ # # This will raise a CouldNotSetAttributeError since `foo.bar` is now "123"
90
+ # expect(foo).not_to allow_value(nil).for(:bar)
91
+ # end
92
+ # end
93
+ #
94
+ # * You're attempting to assert that an numeric attribute should not allow a
95
+ # string that contains non-numeric characters, yet the writer method for
96
+ # that attribute strips out non-numeric characters:
97
+ #
98
+ # class Foo
99
+ # include ActiveModel::Model
100
+ #
101
+ # attr_reader :bar
102
+ #
103
+ # def bar=(value)
104
+ # @bar = value.gsub(/\D+/, '')
105
+ # end
106
+ # end
107
+ #
108
+ # describe Foo do
109
+ # it do
110
+ # foo = Foo.new
111
+ # # This will raise a CouldNotSetAttributeError since `foo.bar` is now "123"
112
+ # expect(foo).not_to allow_value("abc123").for(:bar)
113
+ # end
114
+ # end
115
+ #
116
+ # * You're passing a value to `allow_value` that the model typecasts into
117
+ # another value:
118
+ #
119
+ # describe Foo do
120
+ # # Assume that `attr` is a string
121
+ # # This will raise a CouldNotSetAttributeError since `attr` typecasts `[]` to `"[]"`
122
+ # it { should_not allow_value([]).for(:attr) }
123
+ # end
124
+ #
125
+ # So when you encounter this exception, you have a couple of options:
126
+ #
127
+ # * If you understand the problem and wish to override this behavior to
128
+ # get around this exception, you can add the
129
+ # `ignoring_interference_by_writer` qualifier like so:
130
+ #
131
+ # it do
132
+ # should_not allow_value([]).
133
+ # for(:attr).
134
+ # ignoring_interference_by_writer
135
+ # end
136
+ #
137
+ # * Note, however, that the above option will not always cause the test to
138
+ # pass. In this case, this is telling you that you don't need to use
139
+ # `allow_value`, or quite possibly even the validation that you're
140
+ # testing altogether. In any case, we would probably make the argument
141
+ # that since it's clear that something is responsible for sanitizing
142
+ # incoming data before it's stored in your model, there's no need to
143
+ # ensure that sanitization places the model in a valid state, if such
144
+ # sanitization creates valid data. In terms of testing, the sanitization
145
+ # code should probably be tested, but not the effects of that
146
+ # sanitization on the validness of the model.
60
147
  #
61
148
  # #### Qualifiers
62
149
  #
@@ -82,7 +169,7 @@ module Shoulda
82
169
  # end
83
170
  # end
84
171
  #
85
- # # Test::Unit
172
+ # # Minitest (Shoulda)
86
173
  # class UserProfileTest < ActiveSupport::TestCase
87
174
  # should allow_value('2013-01-01').
88
175
  # for(:birthday_as_string).
@@ -111,7 +198,7 @@ module Shoulda
111
198
  # end
112
199
  # end
113
200
  #
114
- # # Test::Unit
201
+ # # Minitest (Shoulda)
115
202
  # class UserProfileTest < ActiveSupport::TestCase
116
203
  # should allow_value('open', 'closed').
117
204
  # for(:state).
@@ -138,7 +225,7 @@ module Shoulda
138
225
  # end
139
226
  # end
140
227
  #
141
- # # Test::Unit
228
+ # # Minitest (Shoulda)
142
229
  # class UserProfileTest < ActiveSupport::TestCase
143
230
  # should allow_value('open', 'closed').
144
231
  # for(:state).
@@ -176,7 +263,7 @@ module Shoulda
176
263
  # end
177
264
  # end
178
265
  #
179
- # # Test::Unit
266
+ # # Minitest (Shoulda)
180
267
  # class UserProfileTest < ActiveSupport::TestCase
181
268
  # should allow_value('Broncos', 'Titans').
182
269
  # for(:sports_team).
@@ -185,6 +272,32 @@ module Shoulda
185
272
  # )
186
273
  # end
187
274
  #
275
+ # ##### ignoring_interference_by_writer
276
+ #
277
+ # Use `ignoring_interference_by_writer` if you've encountered a
278
+ # CouldNotSetAttributeError and wish to ignore it. Please read the Caveats
279
+ # section above for more information.
280
+ #
281
+ # class Address < ActiveRecord::Base
282
+ # # Address has a zip_code field which is a string
283
+ # end
284
+ #
285
+ # # RSpec
286
+ # describe Address do
287
+ # it do
288
+ # should_not allow_value([]).
289
+ # for(:zip_code).
290
+ # ignoring_interference_by_writer
291
+ # end
292
+ # end
293
+ #
294
+ # # Minitest (Shoulda)
295
+ # class AddressTest < ActiveSupport::TestCase
296
+ # should_not allow_value([]).
297
+ # for(:zip_code).
298
+ # ignoring_interference_by_writer
299
+ # end
300
+ #
188
301
  # @return [AllowValueMatcher]
189
302
  #
190
303
  def allow_value(*values)
@@ -194,6 +307,7 @@ module Shoulda
194
307
  AllowValueMatcher.new(*values)
195
308
  end
196
309
  end
310
+ # @private
197
311
  alias_method :allow_values, :allow_value
198
312
 
199
313
  # @private
@@ -226,6 +340,7 @@ module Shoulda
226
340
  self.options = {}
227
341
  self.after_setting_value_callback = -> {}
228
342
  self.validator = Validator.new
343
+ @ignoring_interference_by_writer = false
229
344
  end
230
345
 
231
346
  def for(attribute)
@@ -255,6 +370,11 @@ module Shoulda
255
370
  self
256
371
  end
257
372
 
373
+ def ignoring_interference_by_writer
374
+ @ignoring_interference_by_writer = true
375
+ self
376
+ end
377
+
258
378
  def _after_setting_value(&callback)
259
379
  self.after_setting_value_callback = callback
260
380
  end
@@ -272,12 +392,10 @@ module Shoulda
272
392
  def failure_message
273
393
  "Did not expect #{expectation},\ngot#{error_description}"
274
394
  end
275
- alias failure_message_for_should failure_message
276
395
 
277
396
  def failure_message_when_negated
278
397
  "Expected #{expectation},\ngot#{error_description}"
279
398
  end
280
- alias failure_message_for_should_not failure_message_when_negated
281
399
 
282
400
  def description
283
401
  validator.allow_description(allowed_values)
@@ -299,6 +417,10 @@ module Shoulda
299
417
  validator.attribute = attribute
300
418
  end
301
419
 
420
+ def ignoring_interference_by_writer?
421
+ @ignoring_interference_by_writer
422
+ end
423
+
302
424
  def value_matches?(value)
303
425
  self.value = value
304
426
  set_attribute(value)
@@ -306,29 +428,15 @@ module Shoulda
306
428
  end
307
429
 
308
430
  def set_attribute(value)
309
- set_attribute_ignoring_range_errors(value)
310
- after_setting_value_callback.call
311
- end
312
-
313
- def set_attribute_ignoring_range_errors(value)
314
431
  instance.__send__("#{attribute_to_set}=", value)
315
- ensure_that_attribute_has_been_changed_to_or_from_nil!(value)
316
- rescue RangeError => exception
317
- # Have to reset the attribute so that we don't get a RangeError the
318
- # next time we attempt to write the attribute (ActiveRecord seems to
319
- # set the attribute to the "bad" value anyway)
320
- reset_attribute
321
- validator.capture_range_error(exception)
322
- end
323
-
324
- def reset_attribute
325
- instance.send(:raw_write_attribute, attribute_to_set, nil)
432
+ ensure_that_attribute_was_set!(value)
433
+ after_setting_value_callback.call
326
434
  end
327
435
 
328
- def ensure_that_attribute_has_been_changed_to_or_from_nil!(expected_value)
436
+ def ensure_that_attribute_was_set!(expected_value)
329
437
  actual_value = instance.__send__(attribute_to_set)
330
438
 
331
- if expected_value.nil? != actual_value.nil?
439
+ if expected_value != actual_value && !ignoring_interference_by_writer?
332
440
  raise CouldNotSetAttributeError.create(
333
441
  instance.class,
334
442
  attribute_to_set,