mocha 2.1.0 → 2.7.1

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -1
  3. data/.yardopts +1 -1
  4. data/Gemfile +6 -1
  5. data/README.md +13 -18
  6. data/RELEASE.md +199 -0
  7. data/Rakefile +9 -9
  8. data/lib/mocha/api.rb +25 -6
  9. data/lib/mocha/cardinality.rb +4 -0
  10. data/lib/mocha/configuration.rb +7 -0
  11. data/lib/mocha/detection/{mini_test.rb → minitest.rb} +5 -5
  12. data/lib/mocha/detection/test_unit.rb +2 -2
  13. data/lib/mocha/expectation.rb +38 -6
  14. data/lib/mocha/expectation_error_factory.rb +2 -2
  15. data/lib/mocha/expectation_list.rb +8 -6
  16. data/lib/mocha/hooks.rb +10 -4
  17. data/lib/mocha/inspect.rb +13 -2
  18. data/lib/mocha/integration/{mini_test → minitest}/adapter.rb +20 -5
  19. data/lib/mocha/integration/{mini_test → minitest}/exception_translation.rb +2 -2
  20. data/lib/mocha/integration/minitest.rb +28 -0
  21. data/lib/mocha/integration/test_unit/adapter.rb +5 -0
  22. data/lib/mocha/minitest.rb +3 -3
  23. data/lib/mocha/mock.rb +37 -14
  24. data/lib/mocha/mockery.rb +13 -9
  25. data/lib/mocha/object_methods.rb +2 -2
  26. data/lib/mocha/parameter_matchers/base.rb +4 -9
  27. data/lib/mocha/parameter_matchers/has_entries.rb +7 -2
  28. data/lib/mocha/parameter_matchers/includes.rb +3 -3
  29. data/lib/mocha/parameter_matchers/instance_methods.rb +9 -10
  30. data/lib/mocha/parameter_matchers/positional_or_keyword_hash.rb +3 -1
  31. data/lib/mocha/parameter_matchers/responds_with.rb +32 -5
  32. data/lib/mocha/parameters_matcher.rb +8 -4
  33. data/lib/mocha/ruby_version.rb +1 -0
  34. data/lib/mocha/version.rb +1 -1
  35. data/mocha.gemspec +9 -1
  36. metadata +15 -9
  37. data/lib/mocha/integration/mini_test.rb +0 -28
@@ -193,6 +193,11 @@ module Mocha
193
193
  #
194
194
  # May be used with Ruby literals or variables for exact matching or with parameter matchers for less-specific matching, e.g. {ParameterMatchers#includes}, {ParameterMatchers#has_key}, etc. See {ParameterMatchers} for a list of all available parameter matchers.
195
195
  #
196
+ # Alternatively a block argument can be passed to {#with} to implement custom parameter matching. The block receives the +*actual_parameters+ as its arguments and should return +true+ if they are acceptable or +false+ otherwise. See the example below where a method is expected to be called with a value divisible by 4.
197
+ # The block argument takes precedence over +expected_parameters_or_matchers+. The block may be called multiple times per invocation of the expected method and so it should be idempotent.
198
+ #
199
+ # Note that if {#with} is called multiple times on the same expectation, the last call takes precedence; other calls are ignored.
200
+ #
196
201
  # Positional arguments were separated from keyword arguments in Ruby v3 (see {https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0 this article}). In relation to this a new configuration option ({Configuration#strict_keyword_argument_matching=}) is available in Ruby >= 2.7.
197
202
  #
198
203
  # When {Configuration#strict_keyword_argument_matching=} is set to +false+ (which is currently the default), a positional +Hash+ and a set of keyword arguments passed to {#with} are treated the same for the purposes of parameter matching. However, a deprecation warning will be displayed if a positional +Hash+ matches a set of keyword arguments or vice versa. This is because {Configuration#strict_keyword_argument_matching=} will default to +true+ in the future.
@@ -202,10 +207,10 @@ module Mocha
202
207
  # @see ParameterMatchers
203
208
  # @see Configuration#strict_keyword_argument_matching=
204
209
  #
205
- # @param [*Array<Object,ParameterMatchers::Base>] expected_parameters_or_matchers expected parameter values or parameter matchers.
210
+ # @param [Array<Object,ParameterMatchers::Base>] expected_parameters_or_matchers expected parameter values or parameter matchers.
206
211
  # @yield optional block specifying custom matching.
207
- # @yieldparam [*Array<Object>] actual_parameters parameters with which expected method was invoked.
208
- # @yieldreturn [Boolean] +true+ if +actual_parameters+ are acceptable.
212
+ # @yieldparam [Array<Object>] actual_parameters parameters with which expected method was invoked.
213
+ # @yieldreturn [Boolean] +true+ if +actual_parameters+ are acceptable; +false+ otherwise.
209
214
  # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
210
215
  #
211
216
  # @example Expected method must be called with exact parameter values.
@@ -256,7 +261,7 @@ module Mocha
256
261
  # example.foo('a', { bar: 'b' })
257
262
  # # This now fails as expected
258
263
  #
259
- # @example Expected method must be called with a value divisible by 4.
264
+ # @example Using a block argument to expect the method to be called with a value divisible by 4.
260
265
  # object = mock()
261
266
  # object.expects(:expected_method).with() { |value| value % 4 == 0 }
262
267
  # object.expected_method(16)
@@ -266,6 +271,22 @@ module Mocha
266
271
  # object.expects(:expected_method).with() { |value| value % 4 == 0 }
267
272
  # object.expected_method(17)
268
273
  # # => verify fails
274
+ #
275
+ # @example Extracting a custom matcher into an instance method on the test class.
276
+ # class MyTest < Minitest::Test
277
+ # def test_expected_method_is_called_with_a_value_divisible_by_4
278
+ # object = mock()
279
+ # object.expects(:expected_method).with(&method(:divisible_by_4))
280
+ # object.expected_method(16)
281
+ # # => verify succeeds
282
+ # end
283
+ #
284
+ # private
285
+ #
286
+ # def divisible_by_4(value)
287
+ # value % 4 == 0
288
+ # end
289
+ # end
269
290
  def with(*expected_parameters_or_matchers, &matching_block)
270
291
  @parameters_matcher = ParametersMatcher.new(expected_parameters_or_matchers, self, &matching_block)
271
292
  self
@@ -632,14 +653,20 @@ module Mocha
632
653
  @ordering_constraints.all?(&:allows_invocation_now?)
633
654
  end
634
655
 
656
+ # @private
657
+ def ordering_constraints_not_allowing_invocation_now
658
+ @ordering_constraints.reject(&:allows_invocation_now?)
659
+ end
660
+
635
661
  # @private
636
662
  def matches_method?(method_name)
637
663
  @method_matcher.match?(method_name)
638
664
  end
639
665
 
640
666
  # @private
641
- def match?(invocation)
642
- @method_matcher.match?(invocation.method_name) && @parameters_matcher.match?(invocation.arguments) && @block_matcher.match?(invocation.block) && in_correct_order?
667
+ def match?(invocation, ignoring_order: false)
668
+ order_independent_match = @method_matcher.match?(invocation.method_name) && @parameters_matcher.match?(invocation.arguments) && @block_matcher.match?(invocation.block)
669
+ ignoring_order ? order_independent_match : order_independent_match && in_correct_order?
643
670
  end
644
671
 
645
672
  # @private
@@ -647,6 +674,11 @@ module Mocha
647
674
  @cardinality.invocations_allowed?
648
675
  end
649
676
 
677
+ # @private
678
+ def invocations_never_allowed?
679
+ @cardinality.invocations_never_allowed?
680
+ end
681
+
650
682
  # @private
651
683
  def satisfied?
652
684
  @cardinality.satisfied?
@@ -6,9 +6,9 @@ module Mocha
6
6
  #
7
7
  # This class should only be used by authors of test libraries and not by typical "users" of Mocha.
8
8
  #
9
- # For example, it is used by +Mocha::Integration::MiniTest::Adapter+ in order to have Mocha raise a +MiniTest::Assertion+ which can then be sensibly handled by +MiniTest::Unit::TestCase+.
9
+ # For example, it is used by +Mocha::Integration::Minitest::Adapter+ in order to have Mocha raise a +Minitest::Assertion+ which can then be sensibly handled by +Minitest::Unit::TestCase+.
10
10
  #
11
- # @see Mocha::Integration::MiniTest::Adapter
11
+ # @see Mocha::Integration::Minitest::Adapter
12
12
  class ExpectationErrorFactory
13
13
  class << self
14
14
  # @!attribute exception_class
@@ -17,14 +17,18 @@ module Mocha
17
17
  @expectations.any? { |expectation| expectation.matches_method?(method_name) }
18
18
  end
19
19
 
20
- def match(invocation)
21
- matching_expectations(invocation).first
20
+ def match(invocation, ignoring_order: false)
21
+ matching_expectations(invocation, ignoring_order: ignoring_order).first
22
22
  end
23
23
 
24
24
  def match_allowing_invocation(invocation)
25
25
  matching_expectations(invocation).detect(&:invocations_allowed?)
26
26
  end
27
27
 
28
+ def match_never_allowing_invocation(invocation)
29
+ matching_expectations(invocation).detect(&:invocations_never_allowed?)
30
+ end
31
+
28
32
  def verified?(assertion_counter = nil)
29
33
  @expectations.all? { |expectation| expectation.verified?(assertion_counter) }
30
34
  end
@@ -49,10 +53,8 @@ module Mocha
49
53
  self.class.new(to_a + other.to_a)
50
54
  end
51
55
 
52
- private
53
-
54
- def matching_expectations(invocation)
55
- @expectations.select { |e| e.match?(invocation) }
56
+ def matching_expectations(invocation, ignoring_order: false)
57
+ @expectations.select { |e| e.match?(invocation, ignoring_order: ignoring_order) }
56
58
  end
57
59
  end
58
60
  end
data/lib/mocha/hooks.rb CHANGED
@@ -7,12 +7,12 @@ module Mocha
7
7
  #
8
8
  # This module is provided as part of the +Mocha::API+ module and is therefore part of the public API, but should only be used by authors of test libraries and not by typical "users" of Mocha.
9
9
  #
10
- # Integration with Test::Unit and MiniTest are provided as part of Mocha, because they are (or were once) part of the Ruby standard library. Integration with other test libraries is not provided as *part* of Mocha, but is supported by means of the methods in this module.
10
+ # Integration with Test::Unit and Minitest are provided as part of Mocha, because they are (or were once) part of the Ruby standard library. Integration with other test libraries is not provided as *part* of Mocha, but is supported by means of the methods in this module.
11
11
  #
12
12
  # See the code in the +Adapter+ modules for examples of how to use the methods in this module. +Mocha::ExpectationErrorFactory+ may be used if you want +Mocha+ to raise a different type of exception.
13
13
  #
14
14
  # @see Mocha::Integration::TestUnit::Adapter
15
- # @see Mocha::Integration::MiniTest::Adapter
15
+ # @see Mocha::Integration::Minitest::Adapter
16
16
  # @see Mocha::ExpectationErrorFactory
17
17
  # @see Mocha::API
18
18
  module Hooks
@@ -35,8 +35,14 @@ module Mocha
35
35
  # Resets Mocha after a test (only for use by authors of test libraries).
36
36
  #
37
37
  # This method should be called after each individual test has finished (including after any "teardown" code).
38
- def mocha_teardown
39
- Mockery.teardown
38
+ def mocha_teardown(origin = mocha_test_name)
39
+ Mockery.teardown(origin)
40
+ end
41
+
42
+ # Returns a string representing the unit test name, to be included in some Mocha
43
+ # to help track down potential bugs.
44
+ def mocha_test_name
45
+ nil
40
46
  end
41
47
  end
42
48
  end
data/lib/mocha/inspect.rb CHANGED
@@ -19,8 +19,19 @@ module Mocha
19
19
 
20
20
  module HashMethods
21
21
  def mocha_inspect
22
- unwrapped = collect { |key, value| "#{key.mocha_inspect} => #{value.mocha_inspect}" }.join(', ')
23
- Hash.ruby2_keywords_hash?(self) ? unwrapped : "{#{unwrapped}}"
22
+ if Hash.ruby2_keywords_hash?(self)
23
+ collect do |key, value|
24
+ case key
25
+ when Symbol
26
+ "#{key}: #{value.mocha_inspect}"
27
+ else
28
+ "#{key.mocha_inspect} => #{value.mocha_inspect}"
29
+ end
30
+ end.join(', ')
31
+ else
32
+ unwrapped = collect { |key, value| "#{key.mocha_inspect} => #{value.mocha_inspect}" }.join(', ')
33
+ "{#{unwrapped}}"
34
+ end
24
35
  end
25
36
  end
26
37
 
@@ -4,21 +4,21 @@ require 'mocha/expectation_error_factory'
4
4
 
5
5
  module Mocha
6
6
  module Integration
7
- module MiniTest
8
- # Integrates Mocha into recent versions of MiniTest.
7
+ module Minitest
8
+ # Integrates Mocha into recent versions of Minitest.
9
9
  #
10
10
  # See the source code for an example of how to integrate Mocha into a test library.
11
11
  module Adapter
12
12
  include Mocha::API
13
13
 
14
14
  # @private
15
- def self.applicable_to?(mini_test_version)
16
- Gem::Requirement.new('>= 3.3.0').satisfied_by?(mini_test_version)
15
+ def self.applicable_to?(minitest_version)
16
+ Gem::Requirement.new('>= 3.3.0').satisfied_by?(minitest_version)
17
17
  end
18
18
 
19
19
  # @private
20
20
  def self.description
21
- 'adapter for MiniTest gem >= v3.3.0'
21
+ 'adapter for Minitest gem >= v3.3.0'
22
22
  end
23
23
 
24
24
  # @private
@@ -46,6 +46,21 @@ module Mocha
46
46
  super
47
47
  mocha_teardown
48
48
  end
49
+
50
+ # @private
51
+ def mocha_test_name
52
+ if respond_to?(:name)
53
+ test_name = name
54
+ elsif respond_to?(:__name__) # Older minitest
55
+ test_name = __name__
56
+ end
57
+
58
+ if test_name
59
+ "#{self.class.name}##{test_name}"
60
+ else
61
+ self.class.name
62
+ end
63
+ end
49
64
  end
50
65
  end
51
66
  end
@@ -2,10 +2,10 @@ require 'mocha/expectation_error'
2
2
 
3
3
  module Mocha
4
4
  module Integration
5
- module MiniTest
5
+ module Minitest
6
6
  def self.translate(exception)
7
7
  return exception unless exception.is_a?(::Mocha::ExpectationError)
8
- translated_exception = ::MiniTest::Assertion.new(exception.message)
8
+ translated_exception = ::Minitest::Assertion.new(exception.message)
9
9
  translated_exception.set_backtrace(exception.backtrace)
10
10
  translated_exception
11
11
  end
@@ -0,0 +1,28 @@
1
+ require 'mocha/debug'
2
+ require 'mocha/detection/minitest'
3
+ require 'mocha/integration/minitest/adapter'
4
+
5
+ module Mocha
6
+ module Integration
7
+ module Minitest
8
+ def self.activate
9
+ target = Detection::Minitest.testcase
10
+ return false unless target
11
+
12
+ minitest_version = Gem::Version.new(Detection::Minitest.version)
13
+ Debug.puts "Detected Minitest version: #{minitest_version}"
14
+
15
+ unless Minitest::Adapter.applicable_to?(minitest_version)
16
+ raise 'Versions of minitest earlier than v3.3.0 are not supported.'
17
+ end
18
+
19
+ unless target < Minitest::Adapter
20
+ Debug.puts "Applying #{Minitest::Adapter.description}"
21
+ target.send(:include, Minitest::Adapter)
22
+ end
23
+
24
+ true
25
+ end
26
+ end
27
+ end
28
+ end
@@ -37,6 +37,11 @@ module Mocha
37
37
 
38
38
  private
39
39
 
40
+ # @private
41
+ def mocha_test_name
42
+ name
43
+ end
44
+
40
45
  # @private
41
46
  def handle_mocha_expectation_error(exception)
42
47
  return false unless exception.is_a?(Mocha::ExpectationError)
@@ -1,6 +1,6 @@
1
1
  require 'mocha/ruby_version'
2
- require 'mocha/integration/mini_test'
2
+ require 'mocha/integration/minitest'
3
3
 
4
- unless Mocha::Integration::MiniTest.activate
5
- raise "MiniTest must be loaded *before* `require 'mocha/minitest'`."
4
+ unless Mocha::Integration::Minitest.activate
5
+ raise "Minitest must be loaded *before* `require 'mocha/minitest'`."
6
6
  end
data/lib/mocha/mock.rb CHANGED
@@ -8,6 +8,7 @@ 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/deprecation'
11
12
 
12
13
  module Mocha
13
14
  # Traditional mock object.
@@ -34,6 +35,9 @@ module Mocha
34
35
  # while an +expects(:foo).at_least_once+ expectation will always be matched
35
36
  # against invocations.
36
37
  #
38
+ # However, note that if the expectation that matches the invocation has a
39
+ # cardinality of "never", then an unexpected invocation error is reported.
40
+ #
37
41
  # This scheme allows you to:
38
42
  #
39
43
  # - Set up default stubs in your the +setup+ method of your test class and
@@ -100,7 +104,7 @@ module Mocha
100
104
  #
101
105
  # @example Setup multiple expectations using +expected_methods_vs_return_values+.
102
106
  # object = mock()
103
- # object.expects(:expected_method_one => :result_one, :expected_method_two => :result_two)
107
+ # object.expects(expected_method_one: :result_one, expected_method_two: :result_two)
104
108
  #
105
109
  # # is exactly equivalent to
106
110
  #
@@ -114,6 +118,7 @@ module Mocha
114
118
  method_name = args.shift
115
119
  ensure_method_not_already_defined(method_name)
116
120
  expectation = Expectation.new(self, method_name, backtrace)
121
+ expectation.in_sequence(@mockery.sequences.last) if @mockery.sequences.any?
117
122
  expectation.returns(args.shift) unless args.empty?
118
123
  @expectations.add(expectation)
119
124
  end
@@ -138,7 +143,7 @@ module Mocha
138
143
  #
139
144
  # @example Setup multiple expectations using +stubbed_methods_vs_return_values+.
140
145
  # object = mock()
141
- # object.stubs(:stubbed_method_one => :result_one, :stubbed_method_two => :result_two)
146
+ # object.stubs(stubbed_method_one: :result_one, stubbed_method_two: :result_two)
142
147
  #
143
148
  # # is exactly equivalent to
144
149
  #
@@ -153,6 +158,7 @@ module Mocha
153
158
  ensure_method_not_already_defined(method_name)
154
159
  expectation = Expectation.new(self, method_name, backtrace)
155
160
  expectation.at_least(0)
161
+ expectation.in_sequence(@mockery.sequences.last) if @mockery.sequences.any?
156
162
  expectation.returns(args.shift) unless args.empty?
157
163
  @expectations.add(expectation)
158
164
  end
@@ -309,22 +315,33 @@ module Mocha
309
315
  end
310
316
 
311
317
  # @private
312
- # rubocop:disable Style/MethodMissingSuper
313
- def method_missing(symbol, *arguments, &block)
318
+ def method_missing(symbol, *arguments, &block) # rubocop:disable Style/MethodMissingSuper
314
319
  handle_method_call(symbol, arguments, block)
315
320
  end
316
321
  ruby2_keywords(:method_missing)
317
- # rubocop:enable Style/MethodMissingSuper
318
322
 
319
323
  # @private
320
- def handle_method_call(symbol, arguments, block)
324
+ def handle_method_call(symbol, arguments, block) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
321
325
  check_expiry
322
326
  check_responder_responds_to(symbol)
323
327
  invocation = Invocation.new(self, symbol, arguments, block)
324
- if (matching_expectation_allowing_invocation = all_expectations.match_allowing_invocation(invocation))
325
- matching_expectation_allowing_invocation.invoke(invocation)
326
- elsif (matching_expectation = all_expectations.match(invocation)) || (!matching_expectation && !@everything_stubbed)
327
- raise_unexpected_invocation_error(invocation, matching_expectation)
328
+
329
+ matching_expectations = all_expectations.matching_expectations(invocation)
330
+
331
+ index = 0
332
+ while index < matching_expectations.length
333
+ matching_expectation = matching_expectations[index]
334
+ if matching_expectation.invocations_never_allowed?
335
+ raise_unexpected_invocation_error(invocation, matching_expectation)
336
+ elsif matching_expectation.invocations_allowed?
337
+ return matching_expectation.invoke(invocation)
338
+ end
339
+ index += 1
340
+ end
341
+
342
+ matching_expectation_ignoring_order = all_expectations.match(invocation, ignoring_order: true)
343
+ if matching_expectation_ignoring_order || (!matching_expectation_ignoring_order && !@everything_stubbed) # rubocop:disable Style/GuardClause
344
+ raise_unexpected_invocation_error(invocation, matching_expectation_ignoring_order)
328
345
  end
329
346
  end
330
347
 
@@ -343,8 +360,8 @@ module Mocha
343
360
  end
344
361
 
345
362
  # @private
346
- def __expire__
347
- @expired = true
363
+ def __expire__(origin)
364
+ @expired = origin || true
348
365
  end
349
366
 
350
367
  # @private
@@ -373,7 +390,11 @@ module Mocha
373
390
  if @unexpected_invocation.nil?
374
391
  @unexpected_invocation = invocation
375
392
  matching_expectation.invoke(invocation) if matching_expectation
376
- message = "#{@unexpected_invocation.call_description}\n#{@mockery.mocha_inspect}"
393
+ call_description = @unexpected_invocation.call_description
394
+ if matching_expectation && !matching_expectation.in_correct_order?
395
+ call_description += ' invoked out of order'
396
+ end
397
+ message = "#{call_description}\n#{@mockery.mocha_inspect}"
377
398
  else
378
399
  message = @unexpected_invocation.short_call_description
379
400
  end
@@ -389,8 +410,10 @@ module Mocha
389
410
  def check_expiry
390
411
  return unless @expired
391
412
 
413
+ origin = @expired == true ? 'one test' : @expired
414
+
392
415
  sentences = [
393
- "#{mocha_inspect} was instantiated in one test but it is receiving invocations within another test.",
416
+ "#{mocha_inspect} was instantiated in #{origin} but it is receiving invocations within another test.",
394
417
  'This can lead to unintended interactions between tests and hence unexpected test failures.',
395
418
  'Ensure that every test correctly cleans up any state that it introduces.'
396
419
  ]
data/lib/mocha/mockery.rb CHANGED
@@ -48,8 +48,8 @@ module Mocha
48
48
  instance.verify(*args)
49
49
  end
50
50
 
51
- def teardown
52
- instance.teardown
51
+ def teardown(origin = nil)
52
+ instance.teardown(origin)
53
53
  ensure
54
54
  @instances.pop
55
55
  end
@@ -91,9 +91,9 @@ module Mocha
91
91
  end
92
92
  end
93
93
 
94
- def teardown
94
+ def teardown(origin = nil)
95
95
  stubba.unstub_all
96
- mocks.each(&:__expire__)
96
+ mocks.each { |m| m.__expire__(origin) }
97
97
  reset
98
98
  end
99
99
 
@@ -109,12 +109,16 @@ module Mocha
109
109
  @state_machines ||= []
110
110
  end
111
111
 
112
+ def sequences
113
+ @sequences ||= []
114
+ end
115
+
112
116
  def mocha_inspect
113
- message = ''
114
- message << "unsatisfied expectations:\n- #{unsatisfied_expectations.map(&:mocha_inspect).join("\n- ")}\n" if unsatisfied_expectations.any?
115
- message << "satisfied expectations:\n- #{satisfied_expectations.map(&:mocha_inspect).join("\n- ")}\n" if satisfied_expectations.any?
116
- message << "states:\n- #{state_machines.map(&:mocha_inspect).join("\n- ")}\n" if state_machines.any?
117
- message
117
+ lines = []
118
+ lines << "unsatisfied expectations:\n- #{unsatisfied_expectations.map(&:mocha_inspect).join("\n- ")}\n" if unsatisfied_expectations.any?
119
+ lines << "satisfied expectations:\n- #{satisfied_expectations.map(&:mocha_inspect).join("\n- ")}\n" if satisfied_expectations.any?
120
+ lines << "states:\n- #{state_machines.map(&:mocha_inspect).join("\n- ")}\n" if state_machines.any?
121
+ lines.join
118
122
  end
119
123
 
120
124
  def on_stubbing(object, method)
@@ -59,7 +59,7 @@ module Mocha
59
59
  #
60
60
  # @example Setting up multiple expectations on a non-mock object.
61
61
  # product = Product.new
62
- # product.expects(:valid? => true, :save => true)
62
+ # product.expects(valid?: true, save: true)
63
63
  #
64
64
  # # exactly equivalent to
65
65
  #
@@ -108,7 +108,7 @@ module Mocha
108
108
  #
109
109
  # @example Setting up multiple stubbed methods on a non-mock object.
110
110
  # product = Product.new
111
- # product.stubs(:valid? => true, :save => true)
111
+ # product.stubs(valid?: true, save: true)
112
112
  #
113
113
  # # exactly equivalent to
114
114
  #
@@ -2,11 +2,6 @@ module Mocha
2
2
  module ParameterMatchers
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
- # @private
6
- def to_matcher(_expectation = nil)
7
- self
8
- end
9
-
10
5
  # A shorthand way of combining two matchers when both must match.
11
6
  #
12
7
  # Returns a new {AllOf} parameter matcher combining two matchers using a logical AND.
@@ -21,12 +16,12 @@ module Mocha
21
16
  # @example Alternative ways to combine matchers with a logical AND.
22
17
  # object = mock()
23
18
  # object.expects(:run).with(all_of(has_key(:foo), has_key(:bar)))
24
- # object.run(:foo => 'foovalue', :bar => 'barvalue')
19
+ # object.run(foo: 'foovalue', bar: 'barvalue')
25
20
  #
26
21
  # # is exactly equivalent to
27
22
  #
28
23
  # object.expects(:run).with(has_key(:foo) & has_key(:bar))
29
- # object.run(:foo => 'foovalue', :bar => 'barvalue)
24
+ # object.run(foo: 'foovalue', bar: 'barvalue)
30
25
  def &(other)
31
26
  AllOf.new(self, other)
32
27
  end
@@ -45,12 +40,12 @@ module Mocha
45
40
  # @example Alternative ways to combine matchers with a logical OR.
46
41
  # object = mock()
47
42
  # object.expects(:run).with(any_of(has_key(:foo), has_key(:bar)))
48
- # object.run(:foo => 'foovalue')
43
+ # object.run(foo: 'foovalue')
49
44
  #
50
45
  # # is exactly equivalent to
51
46
  #
52
47
  # object.expects(:run).with(has_key(:foo) | has_key(:bar))
53
- # object.run(:foo => 'foovalue')
48
+ # object.run(foo: 'foovalue')
54
49
  #
55
50
  # @example Using an explicit {Equals} matcher in combination with {#|}.
56
51
  # object.expects(:run).with(equals(1) | equals(2))
@@ -30,20 +30,25 @@ module Mocha
30
30
  # Parameter matcher which matches when actual parameter contains all expected +Hash+ entries.
31
31
  class HasEntries < Base
32
32
  # @private
33
- def initialize(entries)
33
+ def initialize(entries, exact: false)
34
34
  @entries = entries
35
+ @exact = exact
35
36
  end
36
37
 
37
38
  # @private
38
39
  def matches?(available_parameters)
39
40
  parameter = available_parameters.shift
41
+ return false unless parameter
42
+ return false unless parameter.respond_to?(:keys)
43
+ return false if @exact && @entries.length != parameter.keys.length
44
+
40
45
  has_entry_matchers = @entries.map { |key, value| HasEntry.new(key, value) }
41
46
  AllOf.new(*has_entry_matchers).matches?([parameter])
42
47
  end
43
48
 
44
49
  # @private
45
50
  def mocha_inspect
46
- "has_entries(#{@entries.mocha_inspect})"
51
+ @exact ? @entries.mocha_inspect : "has_entries(#{@entries.mocha_inspect})"
47
52
  end
48
53
  end
49
54
  end
@@ -24,7 +24,7 @@ module Mocha
24
24
  # @example Actual parameter includes item which matches nested matcher.
25
25
  # object = mock()
26
26
  # object.expects(:method_1).with(includes(has_key(:key)))
27
- # object.method_1(['foo', 'bar', {:key => 'baz'}])
27
+ # object.method_1(['foo', 'bar', {key: 'baz'}])
28
28
  # # no error raised
29
29
  #
30
30
  # @example Actual parameter does not include item matching nested matcher.
@@ -44,11 +44,11 @@ module Mocha
44
44
  # @example Actual parameter is a Hash including the given key.
45
45
  # object = mock()
46
46
  # object.expects(:method_1).with(includes(:bar))
47
- # object.method_1({:foo => 1, :bar => 2})
47
+ # object.method_1({foo: 1, bar: 2})
48
48
  # # no error raised
49
49
  #
50
50
  # @example Actual parameter is a Hash without the given key.
51
- # object.method_1({:foo => 1, :baz => 2})
51
+ # object.method_1({foo: 1, baz: 2})
52
52
  # # error raised, because hash does not include key 'bar'
53
53
  #
54
54
  # @example Actual parameter is a Hash with a key matching the given matcher.
@@ -1,3 +1,4 @@
1
+ require 'mocha/parameter_matchers/base'
1
2
  require 'mocha/parameter_matchers/equals'
2
3
  require 'mocha/parameter_matchers/positional_or_keyword_hash'
3
4
 
@@ -6,8 +7,14 @@ module Mocha
6
7
  # @private
7
8
  module InstanceMethods
8
9
  # @private
9
- def to_matcher(_expectation = nil)
10
- Mocha::ParameterMatchers::Equals.new(self)
10
+ def to_matcher(expectation: nil, top_level: false)
11
+ if Base === self
12
+ self
13
+ elsif Hash === self && top_level
14
+ Mocha::ParameterMatchers::PositionalOrKeywordHash.new(self, expectation)
15
+ else
16
+ Mocha::ParameterMatchers::Equals.new(self)
17
+ end
11
18
  end
12
19
  end
13
20
  end
@@ -17,11 +24,3 @@ end
17
24
  class Object
18
25
  include Mocha::ParameterMatchers::InstanceMethods
19
26
  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
@@ -1,6 +1,7 @@
1
1
  require 'mocha/configuration'
2
2
  require 'mocha/deprecation'
3
3
  require 'mocha/parameter_matchers/base'
4
+ require 'mocha/parameter_matchers/has_entries'
4
5
 
5
6
  module Mocha
6
7
  module ParameterMatchers
@@ -13,7 +14,8 @@ module Mocha
13
14
 
14
15
  def matches?(available_parameters)
15
16
  parameter, is_last_parameter = extract_parameter(available_parameters)
16
- return false unless parameter == @value
17
+
18
+ return false unless HasEntries.new(@value, exact: true).matches?([parameter])
17
19
 
18
20
  if is_last_parameter && !same_type_of_hash?(parameter, @value)
19
21
  return false if Mocha.configuration.strict_keyword_argument_matching?