flexmock 2.2.1 → 2.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ac25b7c70e3a49d1404279263c5ad6e5cfb19128
4
- data.tar.gz: 41c3cc477ad1623060a73eacd7dea095226ad6c3
3
+ metadata.gz: 471129d58809ac2dc9a53b22254f76e352f5b253
4
+ data.tar.gz: 3ac4914a786029b8cb1c10750b28f7de602c984b
5
5
  SHA512:
6
- metadata.gz: 486419f39b00d0f8545671d597b7e7832a804545ee40d78bec76a6e46edc68632f16465a7745d0eb4b5bf94fc4612b753134c2c756bdf27ed1c477b71622d827
7
- data.tar.gz: 0bc257bddd171c211d20c1424626a67487387094a43e0e57dd8a7bf49b3923caf4f4947c73ce44e33c98ae7b6443879aba603b44749439d39b8caa38ac9d31f9
6
+ metadata.gz: dcab0eed825f701d642cce8286b9816b087b765beff23fe39205841e1289ecdfc80284a14d59cbbfb90a27d5050c50b25618fd5f27a6f9db4367e2b0b739360c
7
+ data.tar.gz: 8e97cc8946bb2fac86b6b69cb01dec2c42a2cb3aa67d0307dc696e94228bccd66f82cabe1a8c6bdb92b096ba91b3a6889b898e40af714cbc7c7d071b0496a4aa
@@ -1,10 +1,11 @@
1
+ sudo: false
1
2
  language: ruby
2
3
  rvm:
3
4
  - '2.0'
4
- - '2.1'
5
- - '2.2'
5
+ - 2.1.10
6
+ - 2.2.5
6
7
  - 2.3.1
7
- - jruby-9.1.2.0
8
+ - jruby-9.1.5.0
8
9
 
9
10
  script:
10
11
  - rake test
data/README.md CHANGED
@@ -22,6 +22,13 @@ Only significant changes (new APIs, deprecated APIs or backward-compatible
22
22
  changes) are documented here, a.k.a. minor or major version bumps. If you want a
23
23
  detailed changelog, go over the commit log in github (it's pretty low-traffic)
24
24
 
25
+ 2.3.0:
26
+ - implemented validation of call arity for partial mocks. By setting
27
+ FlexMock.partials_verify_signatures = true
28
+ flexmock will verify on partials that the number of arguments, and the
29
+ keyword arguments passed to the mocked call match the existing method's
30
+ signature
31
+
25
32
  2.2.0:
26
33
 
27
34
  - #new_instances now mocks the #initialize method instead of mocking after the
@@ -49,8 +49,10 @@ class FlexMock
49
49
 
50
50
  class << self
51
51
  attr_accessor :partials_are_based
52
+ attr_accessor :partials_verify_signatures
52
53
  end
53
54
  self.partials_are_based = false
55
+ self.partials_verify_signatures = false
54
56
 
55
57
  # Create a FlexMock object with the given name. The name is used in
56
58
  # error messages. If no container is given, create a new, one-off
@@ -153,8 +155,10 @@ class FlexMock
153
155
  def flexmock_based_on(base_class)
154
156
  @base_class = base_class
155
157
  if base_class <= Kernel
156
- should_receive(:class => base_class)
157
- should_receive(:kind_of?).and_return { |against| base_class <= against }
158
+ if self.class != base_class
159
+ should_receive(:class => base_class)
160
+ should_receive(:kind_of?).and_return { |against| base_class <= against }
161
+ end
158
162
  end
159
163
  end
160
164
 
@@ -38,6 +38,7 @@ class FlexMock
38
38
  @location = location
39
39
  @expected_args = nil
40
40
  @count_validators = []
41
+ @signature_validator = SignatureValidator.new(self)
41
42
  @count_validator_class = ExactCountValidator
42
43
  @actual_count = 0
43
44
  @return_value = nil
@@ -65,6 +66,9 @@ class FlexMock
65
66
  @count_validators.each do |validator|
66
67
  result << validator.describe
67
68
  end
69
+ if !@signature_validator.null?
70
+ result << @signature_validator.describe
71
+ end
68
72
  result
69
73
  end
70
74
 
@@ -79,11 +83,18 @@ class FlexMock
79
83
  FlexMock.framework_adapter.check(e.message) { false }
80
84
  end
81
85
 
86
+ def validate_signature(args)
87
+ @signature_validator.validate(args)
88
+ rescue SignatureValidator::ValidationFailed => e
89
+ FlexMock.framework_adapter.check(e.message) { false }
90
+ end
91
+
82
92
  # Verify the current call with the given arguments matches the
83
93
  # expectations recorded in this object.
84
94
  def verify_call(*args)
85
95
  validate_eligible
86
96
  validate_order
97
+ validate_signature(args)
87
98
  @actual_count += 1
88
99
  perform_yielding(args)
89
100
  return_value(args)
@@ -186,6 +197,28 @@ class FlexMock
186
197
  self
187
198
  end
188
199
 
200
+ # Validate general parameters on the call signature
201
+ def with_signature(
202
+ required_arguments: 0, optional_arguments: 0, splat: false,
203
+ required_keyword_arguments: [], optional_keyword_arguments: [], keyword_splat: false)
204
+ @signature_validator = SignatureValidator.new(
205
+ self,
206
+ required_arguments: required_arguments,
207
+ optional_arguments: optional_arguments,
208
+ splat: splat,
209
+ required_keyword_arguments: required_keyword_arguments,
210
+ optional_keyword_arguments: optional_keyword_arguments,
211
+ keyword_splat: keyword_splat)
212
+ self
213
+ end
214
+
215
+ # Validate that the passed arguments match the method signature from the
216
+ # given instance method
217
+ def with_signature_matching(instance_method)
218
+ @signature_validator = SignatureValidator.from_instance_method(self, instance_method)
219
+ self
220
+ end
221
+
189
222
  # :call-seq:
190
223
  # and_return(value)
191
224
  # and_return(value, value, ...)
@@ -40,7 +40,9 @@ class FlexMock
40
40
  call_record.expectation = exp if call_record
41
41
  FlexMock.check(
42
42
  proc { "no matching handler found for " +
43
- FlexMock.format_call(@sym, args) }
43
+ FlexMock.format_call(@sym, args) +
44
+ "\nDefined expectations:\n " +
45
+ @expectations.map(&:description).join("\n ") }
44
46
  ) { !exp.nil? }
45
47
  returned_value = exp.verify_call(*args)
46
48
  returned_value
@@ -58,7 +58,8 @@ class FlexMock
58
58
  :should_receive, :new_instances,
59
59
  :should_receive_with_location,
60
60
  :flexmock_get, :flexmock_teardown, :flexmock_verify,
61
- :flexmock_received?, :flexmock_calls, :flexmock_find_expectation
61
+ :flexmock_received?, :flexmock_calls, :flexmock_find_expectation,
62
+ :invoke_original
62
63
  ]
63
64
 
64
65
  # Initialize a PartialMockProxy object.
@@ -108,12 +109,27 @@ class FlexMock
108
109
  flexmock_define_expectation(caller, *args)
109
110
  end
110
111
 
112
+ # Invoke the original of a mocked method
113
+ #
114
+ # Usually called in a #and_return statement
115
+ def invoke_original(m, *args, &block)
116
+ if block
117
+ args << block
118
+ end
119
+ flexmock_invoke_original(m, args)
120
+ end
121
+
111
122
  def flexmock_define_expectation(location, *args)
112
123
  EXP_BUILDER.parse_should_args(self, args) do |method_name|
113
- unless @methods_proxied.include?(method_name)
124
+ if !@methods_proxied.include?(method_name)
114
125
  hide_existing_method(method_name)
115
126
  end
116
127
  ex = @mock.flexmock_define_expectation(location, method_name)
128
+ if FlexMock.partials_verify_signatures
129
+ if existing_method = @method_definitions[method_name]
130
+ ex.with_signature_matching(existing_method)
131
+ end
132
+ end
117
133
  ex.mock = self
118
134
  ex
119
135
  end
@@ -334,8 +350,9 @@ class FlexMock
334
350
  # not a singleton, all we need to do is override it with our own
335
351
  # singleton.
336
352
  def hide_existing_method(method_name)
337
- stow_existing_definition(method_name)
353
+ existing_method = stow_existing_definition(method_name)
338
354
  define_proxy_method(method_name)
355
+ existing_method
339
356
  end
340
357
 
341
358
  # Stow the existing method definition so that it can be recovered
@@ -345,6 +362,7 @@ class FlexMock
345
362
  @method_definitions[method_name] = target_class_eval { instance_method(method_name) }
346
363
  @methods_proxied << method_name
347
364
  end
365
+ @method_definitions[method_name]
348
366
  rescue NameError
349
367
  end
350
368
 
@@ -9,6 +9,7 @@
9
9
  # above copyright notice is included.
10
10
  #+++
11
11
 
12
+ require 'set'
12
13
  require 'flexmock/noop'
13
14
  require 'flexmock/spy_describers'
14
15
 
@@ -136,4 +137,148 @@ class FlexMock
136
137
  "At most #{@limit}"
137
138
  end
138
139
  end
140
+
141
+ # Validate that the call matches a given signature
142
+ #
143
+ # The validator created by {#initialize} matches any method call
144
+ class SignatureValidator
145
+ class ValidationFailed < RuntimeError
146
+ end
147
+
148
+ # The number of required arguments
149
+ attr_reader :required_arguments
150
+ # The number of optional arguments
151
+ attr_reader :optional_arguments
152
+ # Whether there is a positional argument splat
153
+ def splat?
154
+ @splat
155
+ end
156
+ # The names of required keyword arguments
157
+ # @return [Set<Symbol>]
158
+ attr_reader :required_keyword_arguments
159
+ # The names of optional keyword arguments
160
+ # @return [Set<Symbol>]
161
+ attr_reader :optional_keyword_arguments
162
+ # Whether there is a splat for keyword arguments (double-star)
163
+ def keyword_splat?
164
+ @keyword_splat
165
+ end
166
+
167
+ # Whether this method may have keyword arguments
168
+ def expects_keyword_arguments?
169
+ keyword_splat? || !required_keyword_arguments.empty? || !optional_keyword_arguments.empty?
170
+ end
171
+
172
+ # Whether this method may have keyword arguments
173
+ def requires_keyword_arguments?
174
+ !required_keyword_arguments.empty?
175
+ end
176
+
177
+ def initialize(
178
+ expectation,
179
+ required_arguments: 0,
180
+ optional_arguments: 0,
181
+ splat: true,
182
+ required_keyword_arguments: [],
183
+ optional_keyword_arguments: [],
184
+ keyword_splat: true)
185
+ @exp = expectation
186
+ @required_arguments = required_arguments
187
+ @optional_arguments = optional_arguments
188
+ @required_keyword_arguments = required_keyword_arguments.to_set
189
+ @optional_keyword_arguments = optional_keyword_arguments.to_set
190
+ @splat = splat
191
+ @keyword_splat = keyword_splat
192
+ end
193
+
194
+ # Whether this tests anything
195
+ #
196
+ # It will return if this validator would validate any set of arguments
197
+ def null?
198
+ splat? && keyword_splat?
199
+ end
200
+
201
+ def describe
202
+ ".with_signature(
203
+ required_arguments: #{self.required_arguments},
204
+ optional_arguments: #{self.optional_arguments},
205
+ required_keyword_arguments: #{self.required_keyword_arguments.to_a},
206
+ optional_keyword_arguments: #{self.optional_keyword_arguments.to_a},
207
+ splat: #{self.splat?},
208
+ keyword_splat: #{self.keyword_splat?})"
209
+ end
210
+
211
+ # Validates whether the given arguments match the expected signature
212
+ #
213
+ # @param [Array] args
214
+ # @raise ValidationFailed
215
+ def validate(args)
216
+ kw_args = Hash.new
217
+ if expects_keyword_arguments?
218
+ last_is_kw_hash =
219
+ begin
220
+ args.last.kind_of?(Hash)
221
+ rescue NoMethodError
222
+ false
223
+ end
224
+
225
+ if last_is_kw_hash
226
+ kw_args = args[-1]
227
+ args = args[0..-2]
228
+ elsif requires_keyword_arguments?
229
+ raise ValidationFailed, "#{@exp} expects keyword arguments but none were provided"
230
+ end
231
+ end
232
+
233
+ # There is currently no way to disambiguate "given a block" from "given a
234
+ # proc as last argument" ... give some leeway in this case
235
+
236
+ if required_arguments > args.size
237
+ raise ValidationFailed, "#{@exp} expects at least #{required_arguments} positional arguments but got only #{args.size}"
238
+ elsif !splat? && (required_arguments + optional_arguments) < args.size
239
+ if !args.last.kind_of?(Proc) || (required_arguments + optional_arguments) < args.size - 1
240
+ raise ValidationFailed, "#{@exp} expects at most #{required_arguments + optional_arguments} positional arguments but got #{args.size}"
241
+ end
242
+ end
243
+
244
+ missing_keyword_arguments = required_keyword_arguments.
245
+ find_all { |k| !kw_args.has_key?(k) }
246
+ if !missing_keyword_arguments.empty?
247
+ raise ValidationFailed, "#{@exp} missing required keyword arguments #{missing_keyword_arguments.map(&:to_s).sort.join(", ")}"
248
+ end
249
+ if !keyword_splat?
250
+ kw_args.each_key do |k|
251
+ if !optional_keyword_arguments.include?(k) && !required_keyword_arguments.include?(k)
252
+ raise ValidationFailed, "#{@exp} given unexpected keyword argument #{k}"
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ # Create a validator that represents the signature of an existing method
259
+ def self.from_instance_method(exp, instance_method)
260
+ required_arguments, optional_arguments, splat = 0, 0, false
261
+ required_keyword_arguments, optional_keyword_arguments, keyword_splat = Set.new, Set.new, false
262
+ instance_method.parameters.each do |type, name|
263
+ case type
264
+ when :req then required_arguments += 1
265
+ when :opt then optional_arguments += 1
266
+ when :rest then splat = true
267
+ when :keyreq then required_keyword_arguments << name
268
+ when :key then optional_keyword_arguments << name
269
+ when :keyrest then keyword_splat = true
270
+ when :block
271
+ else raise ArgumentError, "cannot interpret parameter type #{type}"
272
+ end
273
+ end
274
+ new(exp,
275
+ required_arguments: required_arguments,
276
+ optional_arguments: optional_arguments,
277
+ splat: splat,
278
+ required_keyword_arguments: required_keyword_arguments,
279
+ optional_keyword_arguments: optional_keyword_arguments,
280
+ keyword_splat: keyword_splat)
281
+ end
282
+ end
139
283
  end
284
+
@@ -1,3 +1,3 @@
1
1
  class FlexMock
2
- VERSION = "2.2.1"
2
+ VERSION = "2.3.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flexmock
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Weirich
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-07-12 00:00:00.000000000 Z
12
+ date: 2016-10-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest