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
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ module Shoulda::Matchers::Doublespeak
4
+ describe StubImplementation do
5
+ describe '#call' do
6
+ it 'calls #record_call on the double' do
7
+ implementation = described_class.new
8
+ double = build_double
9
+
10
+ double.expects(:record_call).with(:args, :block)
11
+
12
+ implementation.call(double, :object, :args, :block)
13
+ end
14
+
15
+ context 'if no explicit implementation was set' do
16
+ it 'returns nil' do
17
+ implementation = described_class.new
18
+ double = build_double
19
+
20
+ return_value = implementation.call(double, :object, :args, :block)
21
+
22
+ expect(return_value).to eq nil
23
+ end
24
+ end
25
+
26
+ context 'if the implementation was set as a value' do
27
+ it 'returns the set return value' do
28
+ implementation = described_class.new
29
+ implementation.returns(42)
30
+ double = build_double
31
+
32
+ return_value = implementation.call(double, :object, :args, :block)
33
+
34
+ expect(return_value).to eq 42
35
+ end
36
+ end
37
+
38
+ context 'if the implementation was set as a block' do
39
+ it 'calls the block with the object and args/block passed to the method' do
40
+ double = build_double
41
+ expected_object, expected_args, expected_block = :object, :args, :block
42
+ actual_object, actual_args, actual_block = []
43
+ implementation = described_class.new
44
+ implementation.returns do |object, args, block|
45
+ actual_object, actual_args, actual_block = object, args, block
46
+ end
47
+
48
+ implementation.call(
49
+ double,
50
+ expected_object,
51
+ expected_args,
52
+ expected_block
53
+ )
54
+
55
+ expect(actual_object).to eq expected_object
56
+ expect(actual_args).to eq expected_args
57
+ expect(actual_block).to eq expected_block
58
+ end
59
+
60
+ it 'returns the return value of the block' do
61
+ implementation = described_class.new
62
+ implementation.returns { 42 }
63
+ double = build_double
64
+
65
+ return_value = implementation.call(double, :object, :args, :block)
66
+
67
+ expect(return_value).to eq 42
68
+ end
69
+ end
70
+
71
+ context 'if the implementation was set as both a value and a block' do
72
+ it 'prefers the block over the value' do
73
+ implementation = described_class.new
74
+ implementation.returns(:something_else) { 42 }
75
+ double = build_double
76
+
77
+ return_value = implementation.call(double, :object, :args, :block)
78
+
79
+ expect(return_value).to eq 42
80
+ end
81
+ end
82
+ end
83
+
84
+ def build_double
85
+ stub(record_call: nil)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ module Shoulda::Matchers::Doublespeak
4
+ describe World do
5
+ describe '#register_double_collection' do
6
+ it 'calls DoubleCollection.new with the given class' do
7
+ DoubleCollection.expects(:new).with(:klass)
8
+ world = described_class.new
9
+ world.register_double_collection(:klass)
10
+ end
11
+
12
+ it 'returns the newly created DoubleCollection' do
13
+ double_collection = Object.new
14
+ DoubleCollection.stubs(:new).with(:klass).returns(double_collection)
15
+ world = described_class.new
16
+ expect(world.register_double_collection(:klass)).to be double_collection
17
+ end
18
+ end
19
+
20
+ describe '#with_doubles_activated' do
21
+ it 'installs all doubles, yields the block, then uninstalls them all' do
22
+ block_called = false
23
+
24
+ double_collections = Array.new(3) do
25
+ stub.tap do |double_collection|
26
+ sequence = sequence('with_doubles_activated')
27
+ double_collection.expects(:activate).in_sequence(sequence)
28
+ double_collection.expects(:deactivate).in_sequence(sequence)
29
+ end
30
+ end
31
+
32
+ world = described_class.new
33
+
34
+ DoubleCollection.stubs(:new).
35
+ with(:klass1).
36
+ returns(double_collections[0])
37
+ DoubleCollection.stubs(:new).
38
+ with(:klass2).
39
+ returns(double_collections[1])
40
+ DoubleCollection.stubs(:new).
41
+ with(:klass3).
42
+ returns(double_collections[2])
43
+ world.register_double_collection(:klass1)
44
+ world.register_double_collection(:klass2)
45
+ world.register_double_collection(:klass3)
46
+
47
+ world.with_doubles_activated { block_called = true }
48
+
49
+ expect(block_called).to eq true
50
+ end
51
+
52
+ it 'still makes sure to uninstall all doubles even if the block raises an error' do
53
+ double_collection = stub()
54
+ double_collection.stubs(:activate)
55
+ double_collection.expects(:deactivate)
56
+
57
+ world = described_class.new
58
+
59
+ DoubleCollection.stubs(:new).returns(double_collection)
60
+ world.register_double_collection(:klass)
61
+
62
+ begin
63
+ world.with_doubles_activated { raise 'error' }
64
+ rescue RuntimeError
65
+ end
66
+ end
67
+
68
+ it 'does not allow multiple DoubleCollections to be registered that represent the same class' do
69
+ double_collections = [stub, stub]
70
+ sequence = sequence('with_doubles_activated')
71
+ double_collections[0].expects(:activate).never
72
+ double_collections[0].expects(:deactivate).never
73
+ double_collections[1].expects(:activate).in_sequence(sequence)
74
+ double_collections[1].expects(:deactivate).in_sequence(sequence)
75
+
76
+ world = described_class.new
77
+
78
+ DoubleCollection.stubs(:new).
79
+ returns(double_collections[0]).then.
80
+ returns(double_collections[1])
81
+ world.register_double_collection(:klass1)
82
+ world.register_double_collection(:klass1)
83
+
84
+ world.with_doubles_activated { }
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ module Shoulda::Matchers
4
+ describe Doublespeak do
5
+ describe '.register_double_collection' do
6
+ it 'delegates to its world' do
7
+ Doublespeak.world.expects(:register_double_collection).with(:klass)
8
+ described_class.register_double_collection(:klass)
9
+ end
10
+ end
11
+
12
+ describe '.with_doubles_activated' do
13
+ it 'delegates to its world' do
14
+ Doublespeak.world.expects(:with_doubles_activated)
15
+ described_class.with_doubles_activated
16
+ end
17
+ end
18
+ end
19
+ end
@@ -6,8 +6,8 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
6
6
  it 'states that it should delegate method to the right object' do
7
7
  matcher = delegate_method(:method_name).to(:target)
8
8
 
9
- expect(matcher.description)
10
- .to eq 'delegate method #method_name to :target'
9
+ expect(matcher.description).
10
+ to eq 'delegate method #method_name to :target'
11
11
  end
12
12
  end
13
13
 
@@ -22,8 +22,8 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
22
22
 
23
23
  context 'with #with_argument chain' do
24
24
  it 'states that it should delegate method to the right object with right argument' do
25
- matcher = delegate_method(:method_name).to(:target)
26
- .with_arguments(:foo, bar: [1, 2])
25
+ matcher = delegate_method(:method_name).to(:target).
26
+ with_arguments(:foo, bar: [1, 2])
27
27
  message = 'delegate method #method_name to :target with arguments: [:foo, {:bar=>[1, 2]}]'
28
28
 
29
29
  expect(matcher.description).to eq message
@@ -31,16 +31,36 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
31
31
  end
32
32
  end
33
33
 
34
- it 'raises an error if no delegation target is defined' do
34
+ it 'raises an error if the target method was never specified before matching' do
35
35
  expect {
36
- delegate_method(:name).matches?(Object.new)
37
- }.to raise_exception described_class::TargetNotDefinedError
36
+ expect(Object.new).to delegate_method(:name)
37
+ }.to raise_error described_class::TargetNotDefinedError
38
38
  end
39
39
 
40
- it 'raises an error if called with #should_not' do
41
- expect {
42
- delegate_method(:name).to(:anyone).does_not_match?(Object.new)
43
- }.to raise_exception described_class::InvalidDelegateMatcher
40
+ context 'stubbing an instance delegating method' do
41
+ it 'only happens temporarily and is removed after the match' do
42
+ define_class(:company) do
43
+ def name
44
+ 'Acme Company'
45
+ end
46
+ end
47
+
48
+ define_class(:person) do
49
+ def company_name
50
+ company.name
51
+ end
52
+
53
+ def company
54
+ Company.new
55
+ end
56
+ end
57
+
58
+ person = Person.new
59
+ matcher = delegate_method(:company_name).to(:company).as(:name)
60
+ matcher.matches?(person)
61
+
62
+ expect(person.company.name).to eq 'Acme Company'
63
+ end
44
64
  end
45
65
 
46
66
  context 'given a method that does not delegate' do
@@ -54,14 +74,15 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
54
74
 
55
75
  it 'rejects' do
56
76
  post_office = PostOffice.new
57
- matcher = delegate_method(:deliver_mail).to(:mailman)
58
-
59
- expect(matcher.matches?(post_office)).to be false
77
+ expect(post_office).not_to delegate_method(:deliver_mail).to(:mailman)
60
78
  end
61
79
 
62
80
  it 'has a failure message that indicates which method should have been delegated' do
63
81
  post_office = PostOffice.new
64
- message = 'Expected PostOffice#deliver_mail to delegate to PostOffice#mailman'
82
+ message = [
83
+ 'Expected PostOffice#deliver_mail to delegate to PostOffice#mailman',
84
+ 'Calls on PostOffice#mailman: (none)'
85
+ ].join("\n")
65
86
 
66
87
  expect {
67
88
  expect(post_office).to delegate_method(:deliver_mail).to(:mailman)
@@ -69,7 +90,10 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
69
90
  end
70
91
 
71
92
  it 'uses the proper syntax for class methods in errors' do
72
- message = 'Expected PostOffice.deliver_mail to delegate to PostOffice.mailman'
93
+ message = [
94
+ 'Expected PostOffice.deliver_mail to delegate to PostOffice.mailman',
95
+ 'Calls on PostOffice.mailman: (none)'
96
+ ].join("\n")
73
97
 
74
98
  expect {
75
99
  expect(PostOffice).to delegate_method(:deliver_mail).to(:mailman)
@@ -78,7 +102,7 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
78
102
  end
79
103
 
80
104
  context 'given a method that delegates properly' do
81
- it 'accepts' do
105
+ before do
82
106
  define_class(:mailman)
83
107
 
84
108
  define_class(:post_office) do
@@ -90,16 +114,24 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
90
114
  Mailman.new
91
115
  end
92
116
  end
117
+ end
93
118
 
119
+ it 'accepts' do
94
120
  post_office = PostOffice.new
95
-
96
121
  expect(post_office).to delegate_method(:deliver_mail).to(:mailman)
97
122
  end
123
+
124
+ it 'produces the correct failure message if the assertion was negated' do
125
+ post_office = PostOffice.new
126
+ message = 'Expected PostOffice#deliver_mail not to delegate to PostOffice#mailman, but it did'
127
+
128
+ expect {
129
+ expect(post_office).not_to delegate_method(:deliver_mail).to(:mailman)
130
+ }.to fail_with_message(message)
131
+ end
98
132
  end
99
133
 
100
134
  context 'given a method that delegates properly with arguments' do
101
- let(:post_office) { PostOffice.new }
102
-
103
135
  before do
104
136
  define_class(:mailman)
105
137
 
@@ -116,33 +148,49 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
116
148
 
117
149
  context 'when given the correct arguments' do
118
150
  it 'accepts' do
119
- expect(post_office).to delegate_method(:deliver_mail)
120
- .to(:mailman).with_arguments('221B Baker St.', hastily: true)
151
+ post_office = PostOffice.new
152
+ expect(post_office).to delegate_method(:deliver_mail).
153
+ to(:mailman).with_arguments('221B Baker St.', hastily: true)
154
+ end
155
+
156
+ it 'produces the correct failure message if the assertion was negated' do
157
+ post_office = PostOffice.new
158
+ message = 'Expected PostOffice#deliver_mail not to delegate to PostOffice#mailman with arguments: ["221B Baker St.", {:hastily=>true}], but it did'
159
+
160
+ expect {
161
+ expect(post_office).
162
+ not_to delegate_method(:deliver_mail).
163
+ to(:mailman).
164
+ with_arguments('221B Baker St.', hastily: true)
165
+ }.to fail_with_message(message)
121
166
  end
122
167
  end
123
168
 
124
169
  context 'when not given the correct arguments' do
125
170
  it 'rejects' do
126
- matcher = delegate_method(:deliver_mail).to(:mailman)
127
- .with_arguments('123 Nowhere Ln.')
128
-
129
- expect(matcher.matches?(post_office)).to be_false
171
+ post_office = PostOffice.new
172
+ expect(post_office).
173
+ not_to delegate_method(:deliver_mail).to(:mailman).
174
+ with_arguments('123 Nowhere Ln.')
130
175
  end
131
176
 
132
177
  it 'has a failure message that indicates which arguments were expected' do
133
- message = 'Expected PostOffice#deliver_mail to delegate to PostOffice#mailman with arguments: ["123 Nowhere Ln."]'
178
+ post_office = PostOffice.new
179
+ message = [
180
+ 'Expected PostOffice#deliver_mail to delegate to PostOffice#mailman with arguments: ["123 Nowhere Ln."]',
181
+ 'Calls on PostOffice#mailman:',
182
+ '1) deliver_mail("221B Baker St.", {:hastily=>true})'
183
+ ].join("\n")
134
184
 
135
185
  expect {
136
- expect(post_office).to delegate_method(:deliver_mail)
137
- .to(:mailman).with_arguments('123 Nowhere Ln.')
186
+ expect(post_office).to delegate_method(:deliver_mail).
187
+ to(:mailman).with_arguments('123 Nowhere Ln.')
138
188
  }.to fail_with_message(message)
139
189
  end
140
190
  end
141
191
  end
142
192
 
143
193
  context 'given a method that delegates properly to a method of a different name' do
144
- let(:post_office) { PostOffice.new }
145
-
146
194
  before do
147
195
  define_class(:mailman)
148
196
 
@@ -159,24 +207,42 @@ describe Shoulda::Matchers::Independent::DelegateMatcher do
159
207
 
160
208
  context 'when given the correct method name' do
161
209
  it 'accepts' do
162
- expect(post_office).to delegate_method(:deliver_mail)
163
- .to(:mailman).as(:deliver_mail_and_avoid_dogs)
210
+ post_office = PostOffice.new
211
+ expect(post_office).to delegate_method(:deliver_mail).
212
+ to(:mailman).as(:deliver_mail_and_avoid_dogs)
213
+ end
214
+
215
+ it 'produces the correct failure message if the assertion was negated' do
216
+ post_office = PostOffice.new
217
+ message = 'Expected PostOffice#deliver_mail not to delegate to PostOffice#mailman as #deliver_mail_and_avoid_dogs, but it did'
218
+
219
+ expect {
220
+ expect(post_office).
221
+ not_to delegate_method(:deliver_mail).
222
+ to(:mailman).
223
+ as(:deliver_mail_and_avoid_dogs)
224
+ }.to fail_with_message(message)
164
225
  end
165
226
  end
166
227
 
167
228
  context 'when given an incorrect method name' do
168
229
  it 'rejects' do
169
- matcher = delegate_method(:deliver_mail).to(:mailman).as(:watch_tv)
170
-
171
- expect(matcher.matches?(post_office)).to be_false
230
+ post_office = PostOffice.new
231
+ expect(post_office).
232
+ not_to delegate_method(:deliver_mail).to(:mailman).as(:watch_tv)
172
233
  end
173
234
 
174
235
  it 'has a failure message that indicates which method was expected' do
175
- message = 'Expected PostOffice#deliver_mail to delegate to PostOffice#mailman as #watch_tv'
236
+ post_office = PostOffice.new
237
+ message = [
238
+ 'Expected PostOffice#deliver_mail to delegate to PostOffice#mailman as #watch_tv',
239
+ 'Calls on PostOffice#mailman:',
240
+ '1) deliver_mail_and_avoid_dogs()'
241
+ ].join("\n")
176
242
 
177
243
  expect {
178
- expect(post_office).to delegate_method(:deliver_mail)
179
- .to(:mailman).as(:watch_tv)
244
+ expect(post_office).to delegate_method(:deliver_mail).
245
+ to(:mailman).as(:watch_tv)
180
246
  }.to fail_with_message(message)
181
247
  end
182
248
  end
@@ -58,21 +58,30 @@ module ControllerBuilder
58
58
  $test_app.create_temp_view(path, contents)
59
59
  end
60
60
 
61
- def controller_for_resource_with_strong_parameters(options = {}, &block)
62
- define_model "User"
63
- controller_class = define_controller "Users" do
64
- define_method options.fetch(:action) do
65
- @user = User.create(user_params)
61
+ def controller_for_resource_with_strong_parameters(options = {}, &action_body)
62
+ model_name = options.fetch(:model_name, 'User')
63
+ controller_name = options.fetch(:controller_name, 'UsersController')
64
+ collection_name = controller_name.
65
+ to_s.sub(/Controller$/, '').underscore.
66
+ to_sym
67
+ action_name = options.fetch(:action, :some_action)
68
+ routes ||= options.fetch(:routes, -> { resources collection_name })
69
+
70
+ define_model(model_name)
71
+
72
+ controller_class = define_controller(controller_name) do
73
+ define_method action_name do
74
+ if action_body
75
+ instance_eval(&action_body)
76
+ end
77
+
66
78
  render nothing: true
67
79
  end
68
-
69
- private
70
- define_method :user_params, &block
71
80
  end
72
81
 
73
82
  setup_rails_controller_test(controller_class)
74
83
 
75
- define_routes { resources :users }
84
+ define_routes(&routes)
76
85
 
77
86
  controller_class
78
87
  end