flexmock 2.2.1 → 2.3.0

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