shoulda-matchers 2.6.0 → 2.6.1.rc1

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 (52) hide show
  1. data/Gemfile.lock +1 -1
  2. data/NEWS.md +34 -0
  3. data/README.md +14 -0
  4. data/features/activemodel_integration.feature +15 -0
  5. data/features/step_definitions/activemodel_steps.rb +21 -0
  6. data/gemfiles/3.0.gemfile.lock +1 -1
  7. data/gemfiles/3.1.gemfile.lock +1 -1
  8. data/gemfiles/3.2.gemfile.lock +1 -1
  9. data/gemfiles/4.0.0.gemfile.lock +1 -1
  10. data/gemfiles/4.0.1.gemfile.lock +1 -1
  11. data/gemfiles/4.1.gemfile.lock +1 -1
  12. data/lib/shoulda/matchers.rb +1 -0
  13. data/lib/shoulda/matchers/action_controller/callback_matcher.rb +11 -6
  14. data/lib/shoulda/matchers/action_controller/strong_parameters_matcher.rb +59 -95
  15. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +10 -18
  16. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +10 -0
  17. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +60 -18
  18. data/lib/shoulda/matchers/active_model/errors.rb +9 -7
  19. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +4 -0
  20. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +24 -5
  21. data/lib/shoulda/matchers/doublespeak.rb +27 -0
  22. data/lib/shoulda/matchers/doublespeak/double.rb +74 -0
  23. data/lib/shoulda/matchers/doublespeak/double_collection.rb +54 -0
  24. data/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb +27 -0
  25. data/lib/shoulda/matchers/doublespeak/object_double.rb +32 -0
  26. data/lib/shoulda/matchers/doublespeak/proxy_implementation.rb +30 -0
  27. data/lib/shoulda/matchers/doublespeak/structs.rb +8 -0
  28. data/lib/shoulda/matchers/doublespeak/stub_implementation.rb +34 -0
  29. data/lib/shoulda/matchers/doublespeak/world.rb +38 -0
  30. data/lib/shoulda/matchers/independent/delegate_matcher.rb +112 -61
  31. data/lib/shoulda/matchers/integrations/test_unit.rb +8 -6
  32. data/lib/shoulda/matchers/rails_shim.rb +16 -0
  33. data/lib/shoulda/matchers/version.rb +1 -1
  34. data/spec/shoulda/matchers/action_controller/callback_matcher_spec.rb +22 -19
  35. data/spec/shoulda/matchers/action_controller/strong_parameters_matcher_spec.rb +174 -65
  36. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +14 -0
  37. data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +553 -211
  38. data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +6 -0
  39. data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +22 -0
  40. data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +23 -4
  41. data/spec/shoulda/matchers/doublespeak/double_collection_spec.rb +102 -0
  42. data/spec/shoulda/matchers/doublespeak/double_implementation_registry_spec.rb +21 -0
  43. data/spec/shoulda/matchers/doublespeak/double_spec.rb +144 -0
  44. data/spec/shoulda/matchers/doublespeak/object_double_spec.rb +77 -0
  45. data/spec/shoulda/matchers/doublespeak/proxy_implementation_spec.rb +40 -0
  46. data/spec/shoulda/matchers/doublespeak/stub_implementation_spec.rb +88 -0
  47. data/spec/shoulda/matchers/doublespeak/world_spec.rb +88 -0
  48. data/spec/shoulda/matchers/doublespeak_spec.rb +19 -0
  49. data/spec/shoulda/matchers/independent/delegate_matcher_spec.rb +105 -39
  50. data/spec/support/controller_builder.rb +18 -9
  51. data/spec/support/rails_versions.rb +4 -0
  52. metadata +34 -8
@@ -1,5 +1,5 @@
1
1
  module Shoulda
2
2
  module Matchers
3
- VERSION = '2.6.0'.freeze
3
+ VERSION = '2.6.1.rc1'.freeze
4
4
  end
5
5
  end
@@ -2,52 +2,55 @@ require 'spec_helper'
2
2
 
3
3
  describe Shoulda::Matchers::ActionController::CallbackMatcher do
4
4
  shared_examples 'CallbackMatcher' do |kind, callback_type|
5
- let(:matcher) { described_class.new(:authenticate_user!, kind, callback_type) }
6
- let(:controller) { define_controller('HookController') }
5
+ let(:kind) { kind }
6
+ let(:callback_type) { callback_type }
7
+ let(:method_name) { :authenticate_user! }
8
+ let(:matcher) { described_class.new(method_name, kind, callback_type) }
9
+ let(:controller) { define_controller('HookController').new }
10
+
11
+ def match
12
+ __send__("use_#{kind}_#{callback_type}", method_name)
13
+ end
7
14
 
8
- describe '#matches?' do
9
- it "matches when a #{kind} hook is in place" do
10
- add_callback(kind, callback_type, :authenticate_user!)
15
+ it "matches when a #{kind} hook is in place" do
16
+ add_callback(kind, callback_type, method_name)
11
17
 
12
- expect(matcher.matches?(controller)).to be_true
13
- end
18
+ expect(controller).to match
19
+ end
14
20
 
15
- it "does not match when a #{kind} hook is missing" do
16
- expect(matcher.matches?(controller)).to be_false
17
- end
21
+ it "does not match when a #{kind} hook is missing" do
22
+ expect(controller).not_to match
18
23
  end
19
24
 
20
25
  describe 'description' do
21
26
  it 'includes the filter kind and name' do
22
- expect(matcher.description).to eq "have :authenticate_user! as a #{kind}_#{callback_type}"
27
+ expect(matcher.description).to eq "have #{method_name.inspect} as a #{kind}_#{callback_type}"
23
28
  end
24
29
  end
25
30
 
26
31
  describe 'failure message' do
27
32
  it 'includes the filter kind and name that was expected' do
28
- message = "Expected that HookController would have :authenticate_user! as a #{kind}_#{callback_type}"
33
+ message = "Expected that HookController would have #{method_name.inspect} as a #{kind}_#{callback_type}"
29
34
 
30
35
  expect {
31
- expect(controller).to send("use_#{kind}_#{callback_type}", :authenticate_user!)
36
+ expect(controller).to send("use_#{kind}_#{callback_type}", method_name)
32
37
  }.to fail_with_message(message)
33
38
  end
34
39
  end
35
40
 
36
41
  describe 'failure message when negated' do
37
42
  it 'includes the filter kind and name that was expected' do
38
- add_callback(kind, callback_type, :authenticate_user!)
39
- message = "Expected that HookController would not have :authenticate_user! as a #{kind}_#{callback_type}"
43
+ add_callback(kind, callback_type, method_name)
44
+ message = "Expected that HookController would not have #{method_name.inspect} as a #{kind}_#{callback_type}"
40
45
 
41
- expect {
42
- expect(controller).not_to send("use_#{kind}_#{callback_type}", :authenticate_user!)
43
- }.to fail_with_message(message)
46
+ expect { expect(controller).not_to match }.to fail_with_message(message)
44
47
  end
45
48
  end
46
49
 
47
50
  private
48
51
 
49
52
  def add_callback(kind, callback_type, callback)
50
- controller.send("#{kind}_#{callback_type}", callback)
53
+ controller.class.__send__("#{kind}_#{callback_type}", callback)
51
54
  end
52
55
  end
53
56
 
@@ -1,29 +1,29 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Shoulda::Matchers::ActionController do
4
- describe "#permit" do
4
+ describe '#permit' do
5
5
  it 'matches when the sent parameter is allowed' do
6
- controller_class = controller_for_resource_with_strong_parameters(action: :create) do
6
+ controller_for_resource_with_strong_parameters(action: :create) do
7
7
  params.require(:user).permit(:name)
8
8
  end
9
9
 
10
- expect(controller_class).to permit(:name).for(:create)
10
+ expect(@controller).to permit(:name).for(:create)
11
11
  end
12
12
 
13
13
  it 'does not match when the sent parameter is not allowed' do
14
- controller_class = controller_for_resource_with_strong_parameters(action: :create) do
14
+ controller_for_resource_with_strong_parameters(action: :create) do
15
15
  params.require(:user).permit(:name)
16
16
  end
17
17
 
18
- expect(controller_class).not_to permit(:admin).for(:create)
18
+ expect(@controller).not_to permit(:admin).for(:create)
19
19
  end
20
20
 
21
21
  it 'matches against multiple attributes' do
22
- controller_class = controller_for_resource_with_strong_parameters(action: :create) do
22
+ controller_for_resource_with_strong_parameters(action: :create) do
23
23
  params.require(:user).permit(:name, :age)
24
24
  end
25
25
 
26
- expect(controller_class).to permit(:name, :age).for(:create)
26
+ expect(@controller).to permit(:name, :age).for(:create)
27
27
  end
28
28
  end
29
29
  end
@@ -56,145 +56,254 @@ describe Shoulda::Matchers::ActionController::StrongParametersMatcher do
56
56
  end
57
57
  end
58
58
 
59
- describe "#matches?" do
60
- it "is true for a subset of the allowable attributes" do
59
+ describe '#matches?' do
60
+ it 'is true for a subset of the allowable attributes' do
61
61
  controller_for_resource_with_strong_parameters(action: :create) do
62
62
  params.require(:user).permit(:name)
63
63
  end
64
64
 
65
65
  matcher = described_class.new([:name]).in_context(self).for(:create)
66
- expect(matcher.matches?).to be_true
66
+ expect(matcher.matches?(@controller)).to be_true
67
67
  end
68
68
 
69
- it "is true for all the allowable attributes" do
69
+ it 'is true for all the allowable attributes' do
70
70
  controller_for_resource_with_strong_parameters(action: :create) do
71
71
  params.require(:user).permit(:name, :age)
72
72
  end
73
73
 
74
74
  matcher = described_class.new([:name, :age]).in_context(self).for(:create)
75
- expect(matcher.matches?).to be_true
75
+ expect(matcher.matches?(@controller)).to be_true
76
76
  end
77
77
 
78
- it "is false when any attributes are not allowed" do
78
+ it 'is false when any attributes are not allowed' do
79
79
  controller_for_resource_with_strong_parameters(action: :create) do
80
80
  params.require(:user).permit(:name)
81
81
  end
82
82
 
83
83
  matcher = described_class.new([:name, :admin]).in_context(self).for(:create)
84
- expect(matcher.matches?).to be_false
84
+ expect(matcher.matches?(@controller)).to be_false
85
85
  end
86
86
 
87
- it "is false when permit is not called" do
88
- controller_for_resource_with_strong_parameters(action: :create) {}
87
+ it 'is false when permit is not called' do
88
+ controller_for_resource_with_strong_parameters(action: :create)
89
89
 
90
90
  matcher = described_class.new([:name]).in_context(self).for(:create)
91
- expect(matcher.matches?).to be_false
91
+ expect(matcher.matches?(@controller)).to be_false
92
92
  end
93
93
 
94
- it "requires an action" do
94
+ it 'requires an action' do
95
+ controller_for_resource_with_strong_parameters
95
96
  matcher = described_class.new([:name])
96
- expect { matcher.matches? }
97
- .to raise_error(Shoulda::Matchers::ActionController::StrongParametersMatcher::ActionNotDefinedError)
97
+ expect { matcher.matches?(@controller) }.
98
+ to raise_error(described_class::ActionNotDefinedError)
98
99
  end
99
100
 
100
- it "requires a verb for non-restful action" do
101
+ it 'requires a verb for non-restful action' do
102
+ controller_for_resource_with_strong_parameters
101
103
  matcher = described_class.new([:name]).for(:authorize)
102
- expect { matcher.matches? }
103
- .to raise_error(Shoulda::Matchers::ActionController::StrongParametersMatcher::VerbNotDefinedError)
104
+ expect { matcher.matches?(@controller) }.
105
+ to raise_error(described_class::VerbNotDefinedError)
106
+ end
107
+
108
+ it 'works with routes that require extra params' do
109
+ options = {
110
+ controller_name: 'Posts',
111
+ action: :show,
112
+ routes: -> {
113
+ get '/posts/:slug', to: 'posts#show'
114
+ }
115
+ }
116
+ controller_for_resource_with_strong_parameters(options) do
117
+ params.require(:user).permit(:name)
118
+ end
119
+
120
+ matcher = described_class.new([:name]).
121
+ in_context(self).
122
+ for(:show, verb: :get, params: { slug: 'foo' })
123
+ expect(matcher.matches?(@controller)).to be_true
124
+ end
125
+
126
+ it 'works with #update specifically' do
127
+ controller_for_resource_with_strong_parameters(action: :update) do
128
+ params.require(:user).permit(:name)
129
+ end
130
+
131
+ matcher = described_class.new([:name]).
132
+ in_context(self).
133
+ for(:update, params: { id: 1 })
134
+ expect(matcher.matches?(@controller)).to be_true
135
+ end
136
+
137
+ it 'does not raise an error when #fetch was used instead of #require (issue #495)' do
138
+ controller_for_resource_with_strong_parameters(action: :create) do
139
+ params.fetch(:order, {}).permit(:eta, :diner_id)
140
+ end
141
+
142
+ matcher = described_class.new([:eta, :diner_id]).
143
+ in_context(self).
144
+ for(:create)
145
+ expect(matcher.matches?(@controller)).to be_true
146
+ end
147
+
148
+ it 'tracks multiple calls to #permit' do
149
+ sets_of_attributes = [
150
+ [:eta, :diner_id],
151
+ [:phone_number, :address_1, :address_2, :city, :state, :zip]
152
+ ]
153
+ controller_for_resource_with_strong_parameters(action: :create) do
154
+ params.require(:order).permit(sets_of_attributes[0])
155
+ params.require(:diner).permit(sets_of_attributes[1])
156
+ end
157
+
158
+ matcher = described_class.new(sets_of_attributes[0]).
159
+ in_context(self).
160
+ for(:create)
161
+ expect(matcher.matches?(@controller)).to be_true
162
+
163
+ matcher = described_class.new(sets_of_attributes[1]).
164
+ in_context(self).
165
+ for(:create)
166
+ expect(matcher.matches?(@controller)).to be_true
104
167
  end
105
168
 
106
- context 'Stubbing ActionController::Parameters#[]' do
107
- it "does not permanently stub []" do
169
+ context 'stubbing params on the controller' do
170
+ it 'still allows the original params to be set and accessed' do
171
+ actual_user_params = nil
172
+ actual_foo_param = nil
173
+
108
174
  controller_for_resource_with_strong_parameters(action: :create) do
175
+ params[:foo] = 'bar'
176
+ actual_foo_param = params[:foo]
177
+
178
+ actual_user_params = params[:user]
179
+
109
180
  params.require(:user).permit(:name)
110
181
  end
111
182
 
112
- described_class.new([:name]).in_context(self).for(:create).matches?
183
+ matcher = described_class.new([:name]).
184
+ in_context(self).
185
+ for(:create, params: { user: { some: 'params' } })
186
+ matcher.matches?(@controller)
187
+
188
+ expect(actual_user_params).to eq('some' => 'params')
189
+ expect(actual_foo_param).to eq 'bar'
190
+ end
191
+
192
+ it 'stubs the params during the controller action' do
193
+ controller_for_resource_with_strong_parameters(action: :create) do
194
+ params.require(:user)
195
+ end
196
+
197
+ matcher = described_class.new([:name]).in_context(self).for(:create)
198
+
199
+ expect { matcher.matches?(@controller) }.not_to raise_error
200
+ end
201
+
202
+ it 'does not permanently stub params' do
203
+ controller_for_resource_with_strong_parameters(action: :create)
204
+
205
+ matcher = described_class.new([:name]).in_context(self).for(:create)
206
+ matcher.matches?(@controller)
113
207
 
114
- param = ActionController::Parameters.new(name: 'Ralph')[:name]
115
- expect(param.singleton_class).not_to include(
116
- Shoulda::Matchers::ActionController::StrongParametersMatcher::StubbedParameters
117
- )
208
+ expect {
209
+ @controller.params.require(:user)
210
+ }.to raise_error(::ActionController::ParameterMissing)
118
211
  end
119
212
 
120
- it 'prevents permanently overwriting [] on error' do
213
+ it 'prevents permanently stubbing params on error' do
121
214
  stub_controller_with_exception
122
215
 
123
216
  begin
124
- described_class.new([:name]).in_context(self).for(:create).matches?
217
+ matcher = described_class.new([:name]).in_context(self).for(:create)
218
+ matcher.matches?(@controller)
125
219
  rescue SimulatedError
126
220
  end
127
221
 
128
- param = ActionController::Parameters.new(name: 'Ralph')[:name]
129
- expect(param.singleton_class).not_to include(
130
- Shoulda::Matchers::ActionController::StrongParametersMatcher::StubbedParameters
131
- )
222
+ expect {
223
+ @controller.params.require(:user)
224
+ }.to raise_error(::ActionController::ParameterMissing)
132
225
  end
133
226
  end
134
227
  end
135
228
 
136
- describe "failure message" do
137
- it "includes all missing attributes" do
138
- controller_class = controller_for_resource_with_strong_parameters(action: :create) do
229
+ describe 'failure message' do
230
+ it 'includes all missing attributes' do
231
+ controller_for_resource_with_strong_parameters(action: :create) do
139
232
  params.require(:user).permit(:name, :age)
140
233
  end
141
234
 
142
235
  expect {
143
- expect(controller_class).to permit(:name, :age, :city, :country).for(:create)
144
- }.to fail_with_message("Expected controller to permit city and country, but it did not.")
236
+ expect(@controller).to permit(:name, :age, :city, :country).for(:create)
237
+ }.to fail_with_message('Expected controller to permit city and country, but it did not.')
145
238
  end
146
239
 
147
- it "includes all attributes that should not have been allowed but were" do
148
- controller_class = controller_for_resource_with_strong_parameters(action: :create) do
240
+ it 'includes all attributes that should not have been allowed but were' do
241
+ controller_for_resource_with_strong_parameters(action: :create) do
149
242
  params.require(:user).permit(:name, :age)
150
243
  end
151
244
 
152
245
  expect {
153
- expect(controller_class).not_to permit(:name, :age).for(:create)
154
- }.to fail_with_message("Expected controller not to permit name and age, but it did.")
246
+ expect(@controller).not_to permit(:name, :age).for(:create)
247
+ }.to fail_with_message('Expected controller not to permit name and age, but it did.')
155
248
  end
156
249
  end
157
250
 
158
- describe "#for" do
159
- context "when given :create" do
160
- it "posts to the controller" do
161
- context = stub('context', post: nil)
251
+ describe '#for' do
252
+ context 'when given :create' do
253
+ it 'POSTs to the controller' do
254
+ controller = ActionController::Base.new
255
+ context = mock()
256
+ context.expects(:post).with(:create, {})
162
257
  matcher = described_class.new([:name]).in_context(context).for(:create)
163
258
 
164
- matcher.matches?
165
- expect(context).to have_received(:post).with(:create)
259
+ matcher.matches?(controller)
166
260
  end
167
261
  end
168
262
 
169
- context "when given :update" do
170
- it "puts to the controller" do
171
- context = stub('context', put: nil)
172
- matcher = described_class.new([:name]).in_context(context).for(:update)
263
+ context 'when given :update' do
264
+ if rails_gte_41?
265
+ it 'PATCHes to the controller' do
266
+ controller = ActionController::Base.new
267
+ context = mock()
268
+ context.expects(:patch).with(:update, {})
269
+ matcher = described_class.new([:name]).in_context(context).for(:update)
173
270
 
174
- matcher.matches?
175
- expect(context).to have_received(:put).with(:update)
271
+ matcher.matches?(controller)
272
+ end
273
+ else
274
+ it 'PUTs to the controller' do
275
+ controller = ActionController::Base.new
276
+ context = mock()
277
+ context.expects(:put).with(:update, {})
278
+ matcher = described_class.new([:name]).in_context(context).for(:update)
279
+
280
+ matcher.matches?(controller)
281
+ end
176
282
  end
177
283
  end
178
284
 
179
- context "when given a custom action and verb" do
180
- it "deletes to the controller" do
181
- context = stub('context', delete: nil)
182
- matcher = described_class.new([:name]).in_context(context).for(:hide, verb: :delete)
285
+ context 'when given a custom action and verb' do
286
+ it 'calls the action with the verb' do
287
+ controller = ActionController::Base.new
288
+ context = mock()
289
+ context.expects(:delete).with(:hide, {})
290
+ matcher = described_class.new([:name]).
291
+ in_context(context).
292
+ for(:hide, verb: :delete)
183
293
 
184
- matcher.matches?
185
- expect(context).to have_received(:delete).with(:hide)
294
+ matcher.matches?(controller)
186
295
  end
187
296
  end
188
297
  end
189
298
 
190
299
  def stub_controller_with_exception
191
- controller = define_controller('Examples') do
300
+ controller_class = define_controller('Examples') do
192
301
  def create
193
302
  raise SimulatedError
194
303
  end
195
304
  end
196
305
 
197
- setup_rails_controller_test(controller)
306
+ setup_rails_controller_test(controller_class)
198
307
 
199
308
  define_routes do
200
309
  get 'examples', to: 'examples#create'
@@ -24,6 +24,20 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher do
24
24
  end
25
25
  end
26
26
 
27
+ describe '#_after_setting_value' do
28
+ it 'sets a block which is yielded after each value is set on the attribute' do
29
+ attribute = :attr
30
+ record = define_model(:example, attribute => :string).new
31
+ matcher = described_class.new('a', 'b', 'c').for(attribute)
32
+ call_count = 0
33
+
34
+ matcher._after_setting_value { call_count += 1 }
35
+ matcher.matches?(record)
36
+
37
+ expect(call_count).to eq 3
38
+ end
39
+ end
40
+
27
41
  context 'an attribute with a validation' do
28
42
  it 'allows a good value' do
29
43
  expect(validating_format(with: /abc/)).to allow_value('abcde').for(:attr)