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,32 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module Doublespeak
4
+ class ObjectDouble < BasicObject
5
+ attr_reader :calls
6
+
7
+ def initialize
8
+ @calls = []
9
+ @calls_by_method_name = {}
10
+ end
11
+
12
+ def calls_to(method_name)
13
+ @calls_by_method_name[method_name] || []
14
+ end
15
+
16
+ def respond_to?(name, include_private = nil)
17
+ true
18
+ end
19
+
20
+ def method_missing(method_name, *args, &block)
21
+ calls << MethodCallWithName.new(method_name, args, block)
22
+ (calls_by_method_name[method_name] ||= []) << MethodCall.new(args, block)
23
+ nil
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :calls_by_method_name
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module Doublespeak
4
+ class ProxyImplementation
5
+ extend Forwardable
6
+
7
+ DoubleImplementationRegistry.register(self, :proxy)
8
+
9
+ def_delegators :stub_implementation, :returns
10
+
11
+ def self.create
12
+ new(StubImplementation.new)
13
+ end
14
+
15
+ def initialize(stub_implementation)
16
+ @stub_implementation = stub_implementation
17
+ end
18
+
19
+ def call(double, object, args, block)
20
+ stub_implementation.call(double, object, args, block)
21
+ double.call_original_method(object, args, block)
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :stub_implementation
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,8 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module Doublespeak
4
+ MethodCall = Struct.new(:args, :block)
5
+ MethodCallWithName = Struct.new(:method_name, :args, :block)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,34 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module Doublespeak
4
+ class StubImplementation
5
+ DoubleImplementationRegistry.register(self, :stub)
6
+
7
+ def self.create
8
+ new
9
+ end
10
+
11
+ def initialize
12
+ @implementation = proc { nil }
13
+ end
14
+
15
+ def returns(value = nil, &block)
16
+ if block
17
+ @implementation = block
18
+ else
19
+ @implementation = proc { value }
20
+ end
21
+ end
22
+
23
+ def call(double, object, args, block)
24
+ double.record_call(args, block)
25
+ implementation.call(object, args, block)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :implementation
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module Doublespeak
4
+ class World
5
+ def register_double_collection(klass)
6
+ double_collection = DoubleCollection.new(klass)
7
+ double_collections_by_class[klass] = double_collection
8
+ double_collection
9
+ end
10
+
11
+ def with_doubles_activated
12
+ activate
13
+ yield
14
+ ensure
15
+ deactivate
16
+ end
17
+
18
+ private
19
+
20
+ def activate
21
+ double_collections_by_class.each do |klass, double_collection|
22
+ double_collection.activate
23
+ end
24
+ end
25
+
26
+ def deactivate
27
+ double_collections_by_class.each do |klass, double_collection|
28
+ double_collection.deactivate
29
+ end
30
+ end
31
+
32
+ def double_collections_by_class
33
+ @_double_collections_by_class ||= {}
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -5,19 +5,21 @@ module Shoulda # :nodoc:
5
5
  # Ensure that a given method is delegated properly.
6
6
  #
7
7
  # Basic Syntax:
8
- # it { should delegate_method(:deliver_mail).to(:mailman) }
8
+ # it { should delegate_method(method_name).to(delegate_name) }
9
9
  #
10
10
  # Options:
11
- # * <tt>:as</tt> - tests that the object being delegated to is called
12
- # with a certain method (defaults to same name as delegating method)
13
- # * <tt>:with_arguments</tt> - tests that the method on the object being
14
- # delegated to is called with certain arguments
11
+ # * <tt>:as</tt> - The name of the delegating method. Defaults to
12
+ # method_name.
13
+ # * <tt>:with_arguments</tt> - Tests that the delegate method is called
14
+ # with certain arguments.
15
15
  #
16
16
  # Examples:
17
+ # it { should delegate_method(:deliver_mail).to(:mailman) }
17
18
  # it { should delegate_method(:deliver_mail).to(:mailman).
18
- # as(:deliver_with_haste) }
19
+ # as(:deliver_mail_via_mailman) }
19
20
  # it { should delegate_method(:deliver_mail).to(:mailman).
20
- # with_arguments('221B Baker St.', :hastily => true) }
21
+ # as(:deliver_mail_hastily).
22
+ # with_arguments('221B Baker St.', hastily: true) }
21
23
  #
22
24
  def delegate_method(delegating_method)
23
25
  DelegateMatcher.new(delegating_method)
@@ -26,20 +28,23 @@ module Shoulda # :nodoc:
26
28
  class DelegateMatcher
27
29
  def initialize(delegating_method)
28
30
  @delegating_method = delegating_method
31
+ @method_on_target = @delegating_method
32
+ @target_double = Doublespeak::ObjectDouble.new
33
+
29
34
  @delegated_arguments = []
35
+ @target_method = nil
36
+ @subject = nil
37
+ @subject_double_collection = nil
30
38
  end
31
39
 
32
- def matches?(_subject)
33
- @subject = _subject
40
+ def matches?(subject)
41
+ @subject = subject
42
+
34
43
  ensure_target_method_is_present!
35
- stub_target
36
44
 
37
- begin
38
- subject.send(delegating_method, *delegated_arguments)
39
- target_has_received_delegated_method? && target_has_received_arguments?
40
- rescue NoMethodError
41
- false
42
- end
45
+ subject_has_delegating_method? &&
46
+ subject_has_target_method? &&
47
+ subject_delegates_to_target_correctly?
43
48
  end
44
49
 
45
50
  def description
@@ -48,10 +53,6 @@ module Shoulda # :nodoc:
48
53
  )
49
54
  end
50
55
 
51
- def does_not_match?(subject)
52
- raise InvalidDelegateMatcher
53
- end
54
-
55
56
  def to(target_method)
56
57
  @target_method = target_method
57
58
  self
@@ -68,69 +69,77 @@ module Shoulda # :nodoc:
68
69
  end
69
70
 
70
71
  def failure_message
71
- base = "Expected #{delegating_method_name} to delegate to #{target_method_name}"
72
+ base = "Expected #{formatted_delegating_method_name} to delegate to #{formatted_target_method_name}"
72
73
  add_clarifications_to(base)
74
+ base << "\nCalls on #{formatted_target_method_name}:"
75
+ base << formatted_calls_on_target
76
+ base.strip
73
77
  end
74
78
  alias failure_message_for_should failure_message
75
79
 
80
+ def failure_message_when_negated
81
+ base = "Expected #{formatted_delegating_method_name} not to delegate to #{formatted_target_method_name}"
82
+ add_clarifications_to(base)
83
+ base << ', but it did'
84
+ end
85
+ alias failure_message_for_should_not failure_message_when_negated
86
+
76
87
  private
77
88
 
78
- attr_reader :delegated_arguments, :delegating_method, :method, :subject,
79
- :target_method, :method_on_target
89
+ attr_reader \
90
+ :delegated_arguments,
91
+ :delegating_method,
92
+ :method,
93
+ :method_on_target,
94
+ :subject,
95
+ :subject_double_collection,
96
+ :target_double,
97
+ :target_method
80
98
 
81
99
  def add_clarifications_to(message)
82
- if delegated_arguments.present?
100
+ if delegated_arguments.any?
83
101
  message << " with arguments: #{delegated_arguments.inspect}"
84
102
  end
85
103
 
86
- if method_on_target.present?
104
+ if method_on_target != delegating_method
87
105
  message << " as ##{method_on_target}"
88
106
  end
89
107
 
90
108
  message
91
109
  end
92
110
 
93
- def delegating_method_name
94
- method_name_with_class(delegating_method)
111
+ def formatted_delegating_method_name
112
+ formatted_method_name_for(delegating_method)
95
113
  end
96
114
 
97
- def target_method_name
98
- method_name_with_class(target_method)
115
+ def formatted_target_method_name
116
+ formatted_method_name_for(target_method)
99
117
  end
100
118
 
101
- def method_name_with_class(method)
102
- if Class === subject
103
- subject.name + '.' + method.to_s
119
+ def formatted_method_name_for(method_name)
120
+ if subject.is_a?(Class)
121
+ subject.name + '.' + method_name.to_s
104
122
  else
105
- subject.class.name + '#' + method.to_s
123
+ subject.class.name + '#' + method_name.to_s
106
124
  end
107
125
  end
108
126
 
109
- def target_has_received_delegated_method?
110
- stubbed_target.has_received_method?
127
+ def target_received_method?
128
+ calls_to_method_on_target.any?
111
129
  end
112
130
 
113
- def target_has_received_arguments?
114
- stubbed_target.has_received_arguments?(*delegated_arguments)
131
+ def target_received_method_with_delegated_arguments?
132
+ calls_to_method_on_target.any? do |call|
133
+ call.args == delegated_arguments
134
+ end
115
135
  end
116
136
 
117
- def stubbed_method
118
- method_on_target || delegating_method
137
+ def subject_has_delegating_method?
138
+ subject.respond_to?(delegating_method)
119
139
  end
120
140
 
121
- def stub_target
122
- local_stubbed_target = stubbed_target
123
- local_target_method = target_method
124
-
125
- subject.instance_eval do
126
- define_singleton_method local_target_method do
127
- local_stubbed_target
128
- end
129
- end
130
- end
131
-
132
- def stubbed_target
133
- @stubbed_target ||= StubbedTarget.new(stubbed_method)
141
+ def subject_has_target_method?
142
+ subject.respond_to?(target_method)
134
143
  end
135
144
 
136
145
  def ensure_target_method_is_present!
@@ -138,18 +147,60 @@ module Shoulda # :nodoc:
138
147
  raise TargetNotDefinedError
139
148
  end
140
149
  end
141
- end
142
150
 
143
- class DelegateMatcher::TargetNotDefinedError < StandardError
144
- def message
145
- 'Delegation needs a target. Use the #to method to define one, e.g.
146
- `post_office.should delegate(:deliver_mail).to(:mailman)`'.squish
151
+ def subject_delegates_to_target_correctly?
152
+ register_subject_double_collection
153
+
154
+ Doublespeak.with_doubles_activated do
155
+ subject.public_send(delegating_method, *delegated_arguments)
156
+ end
157
+
158
+ if delegated_arguments.any?
159
+ target_received_method_with_delegated_arguments?
160
+ else
161
+ target_received_method?
162
+ end
147
163
  end
148
- end
149
164
 
150
- class DelegateMatcher::InvalidDelegateMatcher < StandardError
151
- def message
152
- '#delegate_to does not support #should_not syntax.'
165
+ def register_subject_double_collection
166
+ double_collection =
167
+ Doublespeak.register_double_collection(subject.singleton_class)
168
+ double_collection.register_stub(target_method).
169
+ to_return(target_double)
170
+
171
+ @subject_double_collection = double_collection
172
+ end
173
+
174
+ def calls_to_method_on_target
175
+ target_double.calls_to(method_on_target)
176
+ end
177
+
178
+ def calls_on_target
179
+ target_double.calls
180
+ end
181
+
182
+ def formatted_calls_on_target
183
+ string = ""
184
+
185
+ if calls_on_target.any?
186
+ string << "\n"
187
+ calls_on_target.each_with_index do |call, i|
188
+ name = call.method_name
189
+ args = call.args.map { |arg| arg.inspect }.join(', ')
190
+ string << "#{i+1}) #{name}(#{args})\n"
191
+ end
192
+ else
193
+ string << " (none)"
194
+ end
195
+
196
+ string
197
+ end
198
+
199
+ class TargetNotDefinedError < StandardError
200
+ def message
201
+ 'Delegation needs a target. Use the #to method to define one, e.g.
202
+ `post_office.should delegate(:deliver_mail).to(:mailman)`'.squish
203
+ end
153
204
  end
154
205
  end
155
206
  end
@@ -21,12 +21,14 @@ end
21
21
 
22
22
  if defined?(ActiveSupport::TestCase)
23
23
  ActiveSupport::TestCase.class_eval do
24
- include Shoulda::Matchers::ActiveRecord
25
- extend Shoulda::Matchers::ActiveRecord
26
- end
24
+ if defined?(Shoulda::Matchers::ActiveRecord)
25
+ include Shoulda::Matchers::ActiveRecord
26
+ extend Shoulda::Matchers::ActiveRecord
27
+ end
27
28
 
28
- ActiveSupport::TestCase.class_eval do
29
- include Shoulda::Matchers::ActiveModel
30
- extend Shoulda::Matchers::ActiveModel
29
+ if defined?(Shoulda::Matchers::ActiveModel)
30
+ include Shoulda::Matchers::ActiveModel
31
+ extend Shoulda::Matchers::ActiveModel
32
+ end
31
33
  end
32
34
  end
@@ -33,6 +33,14 @@ module Shoulda # :nodoc:
33
33
  end
34
34
  end
35
35
 
36
+ def self.verb_for_update
37
+ if action_pack_gte_4_1?
38
+ :patch
39
+ else
40
+ :put
41
+ end
42
+ end
43
+
36
44
  def self.active_record_major_version
37
45
  ::ActiveRecord::VERSION::MAJOR
38
46
  end
@@ -44,6 +52,14 @@ module Shoulda # :nodoc:
44
52
  def self.action_pack_major_version
45
53
  ::ActionPack::VERSION::MAJOR
46
54
  end
55
+
56
+ def self.action_pack_gte_4_1?
57
+ Gem::Requirement.new('>= 4.1').satisfied_by?(action_pack_version)
58
+ end
59
+
60
+ def self.action_pack_version
61
+ Gem::Version.new(::ActionPack::VERSION::STRING)
62
+ end
47
63
  end
48
64
  end
49
65
  end