shoulda-matchers 2.6.0 → 2.6.1.rc1

Sign up to get free protection for your applications and to get access to all the features.
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