mocha 1.12.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -13
  3. data/.yardopts +0 -1
  4. data/CONTRIBUTING.md +1 -1
  5. data/COPYING.md +2 -2
  6. data/Gemfile +27 -0
  7. data/MIT-LICENSE.md +1 -1
  8. data/README.md +12 -8
  9. data/RELEASE.md +169 -0
  10. data/Rakefile +22 -29
  11. data/gemfiles/Gemfile.minitest.latest +1 -0
  12. data/gemfiles/Gemfile.test-unit.latest +2 -5
  13. data/lib/mocha/any_instance_method.rb +0 -5
  14. data/lib/mocha/api.rb +29 -78
  15. data/lib/mocha/backtrace_filter.rb +2 -2
  16. data/lib/mocha/class_methods.rb +2 -2
  17. data/lib/mocha/configuration.rb +36 -114
  18. data/lib/mocha/debug.rb +2 -5
  19. data/lib/mocha/expectation.rb +63 -8
  20. data/lib/mocha/inspect.rb +6 -4
  21. data/lib/mocha/instance_method.rb +0 -4
  22. data/lib/mocha/integration/mini_test/adapter.rb +1 -1
  23. data/lib/mocha/integration/mini_test.rb +10 -38
  24. data/lib/mocha/integration/test_unit/adapter.rb +4 -4
  25. data/lib/mocha/integration/test_unit.rb +10 -33
  26. data/lib/mocha/invocation.rb +12 -16
  27. data/lib/mocha/minitest.rb +2 -4
  28. data/lib/mocha/mock.rb +32 -32
  29. data/lib/mocha/mockery.rb +1 -3
  30. data/lib/mocha/names.rb +1 -1
  31. data/lib/mocha/parameter_matchers/base.rb +1 -1
  32. data/lib/mocha/parameter_matchers/equivalent_uri.rb +1 -2
  33. data/lib/mocha/parameter_matchers/has_entry.rb +22 -13
  34. data/lib/mocha/parameter_matchers/has_keys.rb +53 -0
  35. data/lib/mocha/parameter_matchers/instance_methods.rb +10 -1
  36. data/lib/mocha/parameter_matchers/positional_or_keyword_hash.rb +64 -0
  37. data/lib/mocha/parameter_matchers.rb +1 -0
  38. data/lib/mocha/parameters_matcher.rb +3 -3
  39. data/lib/mocha/ruby_version.rb +1 -2
  40. data/lib/mocha/stubbed_method.rb +5 -42
  41. data/lib/mocha/test_unit.rb +2 -4
  42. data/lib/mocha/version.rb +1 -1
  43. data/lib/mocha.rb +0 -8
  44. data/mocha.gemspec +4 -29
  45. metadata +10 -87
  46. data/bin/build-matrix +0 -82
  47. data/gemfiles/Gemfile.minitest.1.3.0 +0 -7
  48. data/gemfiles/Gemfile.minitest.1.4.0 +0 -7
  49. data/gemfiles/Gemfile.minitest.1.4.1 +0 -7
  50. data/gemfiles/Gemfile.minitest.1.4.2 +0 -7
  51. data/gemfiles/Gemfile.minitest.2.0.0 +0 -7
  52. data/gemfiles/Gemfile.minitest.2.0.1 +0 -7
  53. data/gemfiles/Gemfile.minitest.2.11.0 +0 -7
  54. data/gemfiles/Gemfile.minitest.2.11.2 +0 -7
  55. data/gemfiles/Gemfile.minitest.2.3.0 +0 -7
  56. data/gemfiles/Gemfile.minitest.5.11.3 +0 -7
  57. data/gemfiles/Gemfile.test-unit.2.0.0 +0 -7
  58. data/gemfiles/Gemfile.test-unit.2.0.1 +0 -7
  59. data/gemfiles/Gemfile.test-unit.2.0.3 +0 -7
  60. data/init.rb +0 -1
  61. data/lib/mocha/integration/mini_test/nothing.rb +0 -19
  62. data/lib/mocha/integration/mini_test/version_13.rb +0 -54
  63. data/lib/mocha/integration/mini_test/version_140.rb +0 -54
  64. data/lib/mocha/integration/mini_test/version_141.rb +0 -65
  65. data/lib/mocha/integration/mini_test/version_142_to_172.rb +0 -65
  66. data/lib/mocha/integration/mini_test/version_200.rb +0 -66
  67. data/lib/mocha/integration/mini_test/version_201_to_222.rb +0 -66
  68. data/lib/mocha/integration/mini_test/version_2110_to_2111.rb +0 -70
  69. data/lib/mocha/integration/mini_test/version_2112_to_320.rb +0 -73
  70. data/lib/mocha/integration/mini_test/version_230_to_2101.rb +0 -68
  71. data/lib/mocha/integration/test_unit/gem_version_200.rb +0 -62
  72. data/lib/mocha/integration/test_unit/gem_version_201_to_202.rb +0 -62
  73. data/lib/mocha/integration/test_unit/gem_version_203_to_220.rb +0 -62
  74. data/lib/mocha/integration/test_unit/gem_version_230_to_250.rb +0 -68
  75. data/lib/mocha/integration/test_unit/nothing.rb +0 -19
  76. data/lib/mocha/integration/test_unit/ruby_version_185_and_below.rb +0 -61
  77. data/lib/mocha/integration/test_unit/ruby_version_186_and_above.rb +0 -63
  78. data/lib/mocha/integration.rb +0 -11
  79. data/lib/mocha/setup.rb +0 -14
  80. data/lib/mocha/singleton_class.rb +0 -9
  81. data/yard-templates/default/layout/html/google_analytics.erb +0 -8
  82. data/yard-templates/default/layout/html/setup.rb +0 -5
@@ -12,7 +12,7 @@ module Mocha
12
12
  include Mocha::API
13
13
 
14
14
  # @private
15
- def self.applicable_to?(test_unit_version, _ruby_version = nil)
15
+ def self.applicable_to?(test_unit_version)
16
16
  Gem::Requirement.new('>= 2.5.1').satisfied_by?(test_unit_version)
17
17
  end
18
18
 
@@ -23,16 +23,16 @@ module Mocha
23
23
 
24
24
  # @private
25
25
  def self.included(mod)
26
- mod.setup :mocha_setup, :before => :prepend
26
+ mod.setup :mocha_setup, before: :prepend
27
27
 
28
28
  mod.exception_handler(:handle_mocha_expectation_error)
29
29
 
30
- mod.cleanup :after => :append do
30
+ mod.cleanup after: :append do
31
31
  assertion_counter = Integration::AssertionCounter.new(self)
32
32
  mocha_verify(assertion_counter)
33
33
  end
34
34
 
35
- mod.teardown :mocha_teardown, :after => :append
35
+ mod.teardown :mocha_teardown, after: :append
36
36
  end
37
37
 
38
38
  private
@@ -1,49 +1,26 @@
1
1
  require 'mocha/debug'
2
-
3
2
  require 'mocha/detection/test_unit'
4
-
5
- require 'mocha/integration/test_unit/nothing'
6
- require 'mocha/integration/test_unit/ruby_version_185_and_below'
7
- require 'mocha/integration/test_unit/ruby_version_186_and_above'
8
- require 'mocha/integration/test_unit/gem_version_200'
9
- require 'mocha/integration/test_unit/gem_version_201_to_202'
10
- require 'mocha/integration/test_unit/gem_version_203_to_220'
11
- require 'mocha/integration/test_unit/gem_version_230_to_250'
12
3
  require 'mocha/integration/test_unit/adapter'
13
4
 
14
- require 'mocha/deprecation'
15
-
16
5
  module Mocha
17
6
  module Integration
18
7
  module TestUnit
19
8
  def self.activate
20
- return false unless Detection::TestUnit.testcase
21
- test_unit_version = Gem::Version.new(Detection::TestUnit.version)
22
- ruby_version = Gem::Version.new(RUBY_VERSION.dup)
9
+ target = Detection::TestUnit.testcase
10
+ return false unless target
23
11
 
24
- Debug.puts "Detected Ruby version: #{ruby_version}"
12
+ test_unit_version = Gem::Version.new(Detection::TestUnit.version)
25
13
  Debug.puts "Detected Test::Unit version: #{test_unit_version}"
26
14
 
27
- integration_module = [
28
- TestUnit::Adapter,
29
- TestUnit::GemVersion230To250,
30
- TestUnit::GemVersion203To220,
31
- TestUnit::GemVersion201To202,
32
- TestUnit::GemVersion200,
33
- TestUnit::RubyVersion186AndAbove,
34
- TestUnit::RubyVersion185AndBelow,
35
- TestUnit::Nothing
36
- ].detect { |m| m.applicable_to?(test_unit_version, ruby_version) }
15
+ unless TestUnit::Adapter.applicable_to?(test_unit_version)
16
+ raise 'Versions of test-unit earlier than v2.5.1 are not supported.'
17
+ end
37
18
 
38
- unless ::Test::Unit::TestCase < integration_module
39
- unless integration_module == TestUnit::Adapter
40
- Deprecation.warning(
41
- 'Versions of test-unit earlier than v2.5.1 will not be supported in future versions of Mocha.'
42
- )
43
- end
44
- Debug.puts "Applying #{integration_module.description}"
45
- ::Test::Unit::TestCase.send(:include, integration_module)
19
+ unless target < TestUnit::Adapter
20
+ Debug.puts "Applying #{TestUnit::Adapter.description}"
21
+ target.send(:include, TestUnit::Adapter)
46
22
  end
23
+
47
24
  true
48
25
  end
49
26
  end
@@ -3,14 +3,12 @@ require 'mocha/raised_exception'
3
3
  require 'mocha/return_values'
4
4
  require 'mocha/thrown_object'
5
5
  require 'mocha/yield_parameters'
6
- require 'mocha/configuration'
7
- require 'mocha/deprecation'
8
6
 
9
7
  module Mocha
10
8
  class Invocation
11
9
  attr_reader :method_name, :block
12
10
 
13
- def initialize(mock, method_name, *arguments, &block)
11
+ def initialize(mock, method_name, arguments = [], block = nil)
14
12
  @mock = mock
15
13
  @method_name = method_name
16
14
  @arguments = arguments
@@ -22,18 +20,8 @@ module Mocha
22
20
  def call(yield_parameters = YieldParameters.new, return_values = ReturnValues.new)
23
21
  yield_parameters.next_invocation.each do |yield_args|
24
22
  @yields << ParametersMatcher.new(yield_args)
25
- if @block
26
- @block.call(*yield_args)
27
- else
28
- raise LocalJumpError unless Mocha.configuration.reinstate_undocumented_behaviour_from_v1_9?
29
- yield_args_description = ParametersMatcher.new(yield_args).mocha_inspect
30
- Deprecation.warning(
31
- "Stubbed method was instructed to yield #{yield_args_description}, but no block was given by invocation: #{call_description}.",
32
- ' This will raise a LocalJumpError in the future.',
33
- ' Use Expectation#with_block_given to constrain this expectation to match invocations supplying a block.',
34
- ' And, if necessary, add another expectation to match invocations not supplying a block.'
35
- )
36
- end
23
+ raise LocalJumpError unless @block
24
+ @block.call(*yield_args)
37
25
  end
38
26
  return_values.next(self)
39
27
  end
@@ -55,7 +43,7 @@ module Mocha
55
43
  end
56
44
 
57
45
  def call_description
58
- description = "#{@mock.mocha_inspect}.#{@method_name}#{ParametersMatcher.new(@arguments).mocha_inspect}"
46
+ description = "#{@mock.mocha_inspect}.#{@method_name}#{argument_description}"
59
47
  description << ' { ... }' unless @block.nil?
60
48
  description
61
49
  end
@@ -73,5 +61,13 @@ module Mocha
73
61
  def full_description
74
62
  "\n - #{call_description} #{result_description}"
75
63
  end
64
+
65
+ private
66
+
67
+ def argument_description
68
+ signature = arguments.mocha_inspect
69
+ signature = signature.gsub(/^\[|\]$/, '')
70
+ "(#{signature})"
71
+ end
76
72
  end
77
73
  end
@@ -1,8 +1,6 @@
1
+ require 'mocha/ruby_version'
1
2
  require 'mocha/integration/mini_test'
2
- require 'mocha/deprecation'
3
3
 
4
4
  unless Mocha::Integration::MiniTest.activate
5
- Mocha::Deprecation.warning(
6
- "MiniTest must be loaded *before* `require 'mocha/minitest'`."
7
- )
5
+ raise "MiniTest must be loaded *before* `require 'mocha/minitest'`."
8
6
  end
data/lib/mocha/mock.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'mocha/singleton_class'
1
+ require 'ruby2_keywords'
2
2
  require 'mocha/expectation'
3
3
  require 'mocha/expectation_list'
4
4
  require 'mocha/invocation'
@@ -8,7 +8,6 @@ require 'mocha/method_matcher'
8
8
  require 'mocha/parameters_matcher'
9
9
  require 'mocha/argument_iterator'
10
10
  require 'mocha/expectation_error_factory'
11
- require 'mocha/ruby_version'
12
11
 
13
12
  module Mocha
14
13
  # Traditional mock object.
@@ -109,6 +108,7 @@ module Mocha
109
108
  # object.expects(:expected_method_one).returns(:result_one)
110
109
  # object.expects(:expected_method_two).returns(:result_two)
111
110
  def expects(method_name_or_hash, backtrace = nil)
111
+ expectation = nil
112
112
  iterator = ArgumentIterator.new(method_name_or_hash)
113
113
  iterator.each do |*args|
114
114
  method_name = args.shift
@@ -117,6 +117,7 @@ module Mocha
117
117
  expectation.returns(args.shift) unless args.empty?
118
118
  @expectations.add(expectation)
119
119
  end
120
+ expectation
120
121
  end
121
122
 
122
123
  # Adds an expectation that the specified method may be called any number of times with any parameters.
@@ -145,6 +146,7 @@ module Mocha
145
146
  # object.stubs(:stubbed_method_one).returns(:result_one)
146
147
  # object.stubs(:stubbed_method_two).returns(:result_two)
147
148
  def stubs(method_name_or_hash, backtrace = nil)
149
+ expectation = nil
148
150
  iterator = ArgumentIterator.new(method_name_or_hash)
149
151
  iterator.each do |*args|
150
152
  method_name = args.shift
@@ -154,6 +156,7 @@ module Mocha
154
156
  expectation.returns(args.shift) unless args.empty?
155
157
  @expectations.add(expectation)
156
158
  end
159
+ expectation
157
160
  end
158
161
 
159
162
  # Removes the specified stubbed methods (added by calls to {#expects} or {#stubs}) and all expectations associated with them.
@@ -161,7 +164,7 @@ module Mocha
161
164
  # @param [Array<Symbol>] method_names names of methods to unstub.
162
165
  #
163
166
  # @example Invoking an unstubbed method causes error to be raised
164
- # object = mock('mock') do
167
+ # object = mock('mock')
165
168
  # object.stubs(:stubbed_method).returns(:result_one)
166
169
  # object.stubbed_method # => :result_one
167
170
  # object.unstub(:stubbed_method)
@@ -180,11 +183,11 @@ module Mocha
180
183
  end
181
184
  end
182
185
 
183
- # Constrains the {Mock} instance so that it can only expect or stub methods to which +responder+ responds. The constraint is only applied at method invocation time.
186
+ # Constrains the {Mock} instance so that it can only expect or stub methods to which +responder+ responds publicly. The constraint is only applied at method invocation time.
184
187
  #
185
- # A +NoMethodError+ will be raised if the +responder+ does not +#respond_to?+ a method invocation (even if the method has been expected or stubbed).
188
+ # A +NoMethodError+ will be raised if the +responder+ does not publicly +#respond_to?+ the invoked method (even if the method has been expected or stubbed).
186
189
  #
187
- # The {Mock} instance will delegate its +#respond_to?+ method to the +responder+.
190
+ # The {Mock} instance will delegate its +#respond_to?+ method to the +responder+. However, the +include_all+ parameter is not passed through, so only public methods on the +responder+ will be considered.
188
191
  #
189
192
  # Note that the methods on +responder+ are never actually invoked.
190
193
  #
@@ -234,11 +237,11 @@ module Mocha
234
237
  self
235
238
  end
236
239
 
237
- # Constrains the {Mock} instance so that it can only expect or stub methods to which an instance of the +responder_class+ responds. The constraint is only applied at method invocation time. Note that the responder instance is instantiated using +Class#allocate+.
240
+ # Constrains the {Mock} instance so that it can only expect or stub methods to which an instance of the +responder_class+ responds publicly. The constraint is only applied at method invocation time. Note that the responder instance is instantiated using +Class#allocate+.
238
241
  #
239
- # A +NoMethodError+ will be raised if the responder instance does not +#respond_to?+ a method invocation (even if the method has been expected or stubbed).
242
+ # A +NoMethodError+ will be raised if the responder instance does not publicly +#respond_to?+ the invoked method (even if the method has been expected or stubbed).
240
243
  #
241
- # The {Mock} instance will delegate its +#respond_to?+ method to the responder instance.
244
+ # The {Mock} instance will delegate its +#respond_to?+ method to the responder instance. However, the +include_all+ parameter is not passed through, so only public methods on the +responder+ will be considered.
242
245
  #
243
246
  # Note that the methods on the responder instance are never actually invoked.
244
247
  #
@@ -306,10 +309,18 @@ module Mocha
306
309
  end
307
310
 
308
311
  # @private
309
- def method_missing(symbol, *arguments, &block) # rubocop:disable Style/MethodMissingSuper
312
+ # rubocop:disable Style/MethodMissingSuper
313
+ def method_missing(symbol, *arguments, &block)
314
+ handle_method_call(symbol, arguments, block)
315
+ end
316
+ ruby2_keywords(:method_missing)
317
+ # rubocop:enable Style/MethodMissingSuper
318
+
319
+ # @private
320
+ def handle_method_call(symbol, arguments, block)
310
321
  check_expiry
311
322
  check_responder_responds_to(symbol)
312
- invocation = Invocation.new(self, symbol, *arguments, &block)
323
+ invocation = Invocation.new(self, symbol, arguments, block)
313
324
  if (matching_expectation_allowing_invocation = all_expectations.match_allowing_invocation(invocation))
314
325
  matching_expectation_allowing_invocation.invoke(invocation)
315
326
  elsif (matching_expectation = all_expectations.match(invocation)) || (!matching_expectation && !@everything_stubbed)
@@ -318,25 +329,14 @@ module Mocha
318
329
  end
319
330
 
320
331
  # @private
321
- def respond_to_missing?(symbol, include_private = false)
332
+ def respond_to_missing?(symbol, _include_all)
322
333
  if @responder
323
- if @responder.method(:respond_to?).arity > 1
324
- @responder.respond_to?(symbol, include_private)
325
- else
326
- @responder.respond_to?(symbol)
327
- end
334
+ @responder.respond_to?(symbol)
328
335
  else
329
336
  @everything_stubbed || all_expectations.matches_method?(symbol)
330
337
  end
331
338
  end
332
339
 
333
- if PRE_RUBY_V19
334
- # @private
335
- def respond_to?(symbol, include_private = false)
336
- respond_to_missing?(symbol, include_private)
337
- end
338
- end
339
-
340
340
  # @private
341
341
  def __verified__?(assertion_counter = nil)
342
342
  @expectations.verified?(assertion_counter)
@@ -387,14 +387,14 @@ module Mocha
387
387
  end
388
388
 
389
389
  def check_expiry
390
- if @expired # rubocop:disable Style/GuardClause
391
- Deprecation.warning(
392
- "#{mocha_inspect} was instantiated in one test but it is receiving invocations within another test.",
393
- ' This can lead to unintended interactions between tests and hence unexpected test failures.',
394
- ' Ensure that every test correctly cleans up any state that it introduces.',
395
- ' A Mocha::StubbingError will be raised in this scenario in the future.'
396
- )
397
- end
390
+ return unless @expired
391
+
392
+ sentences = [
393
+ "#{mocha_inspect} was instantiated in one test but it is receiving invocations within another test.",
394
+ 'This can lead to unintended interactions between tests and hence unexpected test failures.',
395
+ 'Ensure that every test correctly cleans up any state that it introduces.'
396
+ ]
397
+ raise StubbingError.new(sentences.join(' '), caller)
398
398
  end
399
399
  end
400
400
  end
data/lib/mocha/mockery.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'mocha/ruby_version'
2
1
  require 'mocha/central'
3
2
  require 'mocha/mock'
4
3
  require 'mocha/names'
@@ -119,10 +118,9 @@ module Mocha
119
118
  end
120
119
 
121
120
  def on_stubbing(object, method)
122
- method = PRE_RUBY_V19 ? method.to_s : method.to_sym
123
121
  signature_proc = lambda { "#{object.mocha_inspect}.#{method}" }
124
122
  check(:stubbing_non_existent_method, 'non-existent method', signature_proc) do
125
- !(object.stubba_class.__method_exists__?(method, true) || object.respond_to?(method.to_sym))
123
+ !(object.stubba_class.__method_exists__?(method, true) || object.respond_to?(method))
126
124
  end
127
125
  check(:stubbing_non_public_method, 'non-public method', signature_proc) do
128
126
  object.stubba_class.__method_exists__?(method, false)
data/lib/mocha/names.rb CHANGED
@@ -37,7 +37,7 @@ module Mocha
37
37
  def mocha_inspect
38
38
  address = @mock.__id__ * 2
39
39
  address += 0x100000000 if address < 0
40
- "#<Mock:0x#{format('%x', address)}>"
40
+ "#<Mock:0x#{format('%<address>x', address: address)}>"
41
41
  end
42
42
  end
43
43
  end
@@ -3,7 +3,7 @@ module Mocha
3
3
  # @abstract Subclass and implement +#matches?+ and +#mocha_inspect+ to define a custom matcher. Also add a suitably named instance method to {ParameterMatchers} to build an instance of the new matcher c.f. {#equals}.
4
4
  class Base
5
5
  # @private
6
- def to_matcher
6
+ def to_matcher(_expectation = nil)
7
7
  self
8
8
  end
9
9
 
@@ -1,4 +1,3 @@
1
- require 'mocha/deprecation'
2
1
  require 'mocha/parameter_matchers/base'
3
2
  require 'uri'
4
3
  require 'cgi'
@@ -51,7 +50,7 @@ module Mocha
51
50
  # @private
52
51
  def explode(uri)
53
52
  query_hash = CGI.parse(uri.query || '')
54
- URI::Generic::COMPONENT.inject({}) { |h, k| h.merge(k => uri.__send__(k)) }.merge(:query => query_hash)
53
+ URI::Generic::COMPONENT.inject({}) { |h, k| h.merge(k => uri.__send__(k)) }.merge(query: query_hash)
55
54
  end
56
55
  end
57
56
  end
@@ -42,20 +42,10 @@ module Mocha
42
42
  #
43
43
  def has_entry(*options) # rubocop:disable Naming/PredicateName
44
44
  case options.length
45
+ when 0
46
+ raise ArgumentError, 'No arguments. Expecting at least one.'
45
47
  when 1
46
- case options[0]
47
- when Hash
48
- case options[0].length
49
- when 0
50
- raise ArgumentError, 'Argument has no entries.'
51
- when 1
52
- key, value = options[0].first
53
- else
54
- raise ArgumentError, 'Argument has multiple entries. Use Mocha::ParameterMatchers#has_entries instead.'
55
- end
56
- else
57
- raise ArgumentError, 'Argument is not a Hash.'
58
- end
48
+ key, value = parse_option(options[0])
59
49
  when 2
60
50
  key, value = options
61
51
  else
@@ -85,5 +75,24 @@ module Mocha
85
75
  "has_entry(#{@key.mocha_inspect} => #{@value.mocha_inspect})"
86
76
  end
87
77
  end
78
+
79
+ private
80
+
81
+ # @private
82
+ def parse_option(option)
83
+ case option
84
+ when Hash
85
+ case option.length
86
+ when 0
87
+ raise ArgumentError, 'Argument has no entries.'
88
+ when 1
89
+ option.first
90
+ else
91
+ raise ArgumentError, 'Argument has multiple entries. Use Mocha::ParameterMatchers#has_entries instead.'
92
+ end
93
+ else
94
+ raise ArgumentError, 'Argument is not a Hash.'
95
+ end
96
+ end
88
97
  end
89
98
  end
@@ -0,0 +1,53 @@
1
+ require 'mocha/parameter_matchers/base'
2
+
3
+ module Mocha
4
+ module ParameterMatchers
5
+ # Matches +Hash+ containing +keys+.
6
+ #
7
+ # @param [*Array<Object>] keys expected keys.
8
+ # @return [HasKeys] parameter matcher.
9
+ #
10
+ # @see Expectation#with
11
+ #
12
+ # @example Actual parameter contains entry with expected keys.
13
+ # object = mock()
14
+ # object.expects(:method_1).with(has_keys(:key_1, :key_2))
15
+ # object.method_1(:key_1 => 1, :key_2 => 2, :key_3 => 3)
16
+ # # no error raised
17
+ #
18
+ # @example Actual parameter does not contain all expected keys.
19
+ # object = mock()
20
+ # object.expects(:method_1).with(has_keys(:key_1, :key_2))
21
+ # object.method_1(:key_2 => 2)
22
+ # # error raised, because method_1 was not called with Hash containing key: :key_1
23
+ #
24
+ def has_keys(*keys) # rubocop:disable Naming/PredicateName
25
+ HasKeys.new(*keys)
26
+ end
27
+
28
+ # Parameter matcher which matches when actual parameter contains +Hash+ with all expected keys.
29
+ class HasKeys < Base
30
+ # @private
31
+ def initialize(*keys)
32
+ raise ArgumentError, 'No arguments. Expecting at least one.' if keys.empty?
33
+
34
+ @keys = keys
35
+ end
36
+
37
+ # @private
38
+ def matches?(available_parameters)
39
+ parameter = available_parameters.shift
40
+ return false unless parameter.respond_to?(:keys)
41
+
42
+ @keys.map(&:to_matcher).all? do |matcher|
43
+ parameter.keys.any? { |key| matcher.matches?([key]) }
44
+ end
45
+ end
46
+
47
+ # @private
48
+ def mocha_inspect
49
+ "has_keys(#{@keys.mocha_inspect(false)})"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,11 +1,12 @@
1
1
  require 'mocha/parameter_matchers/equals'
2
+ require 'mocha/parameter_matchers/positional_or_keyword_hash'
2
3
 
3
4
  module Mocha
4
5
  module ParameterMatchers
5
6
  # @private
6
7
  module InstanceMethods
7
8
  # @private
8
- def to_matcher
9
+ def to_matcher(_expectation = nil)
9
10
  Mocha::ParameterMatchers::Equals.new(self)
10
11
  end
11
12
  end
@@ -16,3 +17,11 @@ end
16
17
  class Object
17
18
  include Mocha::ParameterMatchers::InstanceMethods
18
19
  end
20
+
21
+ # @private
22
+ class Hash
23
+ # @private
24
+ def to_matcher(expectation = nil)
25
+ Mocha::ParameterMatchers::PositionalOrKeywordHash.new(self, expectation)
26
+ end
27
+ end
@@ -0,0 +1,64 @@
1
+ require 'mocha/configuration'
2
+ require 'mocha/deprecation'
3
+ require 'mocha/parameter_matchers/base'
4
+
5
+ module Mocha
6
+ module ParameterMatchers
7
+ # @private
8
+ class PositionalOrKeywordHash < Base
9
+ def initialize(value, expectation)
10
+ @value = value
11
+ @expectation = expectation
12
+ end
13
+
14
+ def matches?(available_parameters)
15
+ parameter, is_last_parameter = extract_parameter(available_parameters)
16
+ return false unless parameter == @value
17
+
18
+ if is_last_parameter && !same_type_of_hash?(parameter, @value)
19
+ return false if Mocha.configuration.strict_keyword_argument_matching?
20
+
21
+ deprecation_warning(parameter, @value) if Mocha::RUBY_V27_PLUS
22
+ end
23
+
24
+ true
25
+ end
26
+
27
+ def mocha_inspect
28
+ @value.mocha_inspect
29
+ end
30
+
31
+ private
32
+
33
+ def extract_parameter(available_parameters)
34
+ [available_parameters.shift, available_parameters.empty?]
35
+ end
36
+
37
+ def same_type_of_hash?(actual, expected)
38
+ ruby2_keywords_hash?(actual) == ruby2_keywords_hash?(expected)
39
+ end
40
+
41
+ def deprecation_warning(actual, expected)
42
+ details1 = "Expectation #{expectation_definition} expected #{hash_type(expected)} (#{expected.mocha_inspect}),".squeeze(' ')
43
+ details2 = "but received #{hash_type(actual)} (#{actual.mocha_inspect})."
44
+ sentence1 = 'These will stop matching when strict keyword argument matching is enabled.'
45
+ sentence2 = 'See the documentation for Mocha::Configuration#strict_keyword_argument_matching=.'
46
+ Deprecation.warning([details1, details2, sentence1, sentence2].join(' '))
47
+ end
48
+
49
+ def hash_type(hash)
50
+ ruby2_keywords_hash?(hash) ? 'keyword arguments' : 'positional hash'
51
+ end
52
+
53
+ def ruby2_keywords_hash?(hash)
54
+ hash.is_a?(Hash) && ::Hash.ruby2_keywords_hash?(hash)
55
+ end
56
+
57
+ def expectation_definition
58
+ return nil unless @expectation
59
+
60
+ "defined at #{@expectation.definition_location}"
61
+ end
62
+ end
63
+ end
64
+ end
@@ -13,6 +13,7 @@ require 'mocha/parameter_matchers/equals'
13
13
  require 'mocha/parameter_matchers/has_entry'
14
14
  require 'mocha/parameter_matchers/has_entries'
15
15
  require 'mocha/parameter_matchers/has_key'
16
+ require 'mocha/parameter_matchers/has_keys'
16
17
  require 'mocha/parameter_matchers/has_value'
17
18
  require 'mocha/parameter_matchers/includes'
18
19
  require 'mocha/parameter_matchers/instance_of'
@@ -3,8 +3,9 @@ require 'mocha/parameter_matchers'
3
3
 
4
4
  module Mocha
5
5
  class ParametersMatcher
6
- def initialize(expected_parameters = [ParameterMatchers::AnyParameters.new], &matching_block)
6
+ def initialize(expected_parameters = [ParameterMatchers::AnyParameters.new], expectation = nil, &matching_block)
7
7
  @expected_parameters = expected_parameters
8
+ @expectation = expectation
8
9
  @matching_block = matching_block
9
10
  end
10
11
 
@@ -23,12 +24,11 @@ module Mocha
23
24
  def mocha_inspect
24
25
  signature = matchers.mocha_inspect
25
26
  signature = signature.gsub(/^\[|\]$/, '')
26
- signature = signature.gsub(/^\{|\}$/, '') if matchers.length == 1
27
27
  "(#{signature})"
28
28
  end
29
29
 
30
30
  def matchers
31
- @expected_parameters.map(&:to_matcher)
31
+ @expected_parameters.map { |p| p.to_matcher(@expectation) }
32
32
  end
33
33
  end
34
34
  end
@@ -1,4 +1,3 @@
1
1
  module Mocha
2
- PRE_RUBY_V19 = Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('1.9')
3
- RUBY_V2_PLUS = Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2')
2
+ RUBY_V27_PLUS = Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.7')
4
3
  end