mocha 1.16.1 → 2.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -13
  3. data/.yardopts +1 -2
  4. data/COPYING.md +2 -2
  5. data/Gemfile +7 -4
  6. data/MIT-LICENSE.md +1 -1
  7. data/README.md +23 -24
  8. data/RELEASE.md +294 -0
  9. data/Rakefile +23 -24
  10. data/lib/mocha/api.rb +30 -71
  11. data/lib/mocha/backtrace_filter.rb +2 -2
  12. data/lib/mocha/cardinality.rb +4 -0
  13. data/lib/mocha/configuration.rb +44 -126
  14. data/lib/mocha/debug.rb +2 -5
  15. data/lib/mocha/detection/{mini_test.rb → minitest.rb} +5 -5
  16. data/lib/mocha/detection/test_unit.rb +2 -2
  17. data/lib/mocha/expectation.rb +99 -12
  18. data/lib/mocha/expectation_error_factory.rb +2 -2
  19. data/lib/mocha/expectation_list.rb +8 -6
  20. data/lib/mocha/hooks.rb +10 -4
  21. data/lib/mocha/inspect.rb +15 -4
  22. data/lib/mocha/integration/{mini_test → minitest}/adapter.rb +21 -6
  23. data/lib/mocha/integration/{mini_test → minitest}/exception_translation.rb +2 -2
  24. data/lib/mocha/integration/minitest.rb +28 -0
  25. data/lib/mocha/integration/test_unit/adapter.rb +9 -4
  26. data/lib/mocha/integration/test_unit.rb +10 -31
  27. data/lib/mocha/invocation.rb +2 -15
  28. data/lib/mocha/minitest.rb +3 -6
  29. data/lib/mocha/mock.rb +45 -18
  30. data/lib/mocha/mockery.rb +13 -9
  31. data/lib/mocha/names.rb +1 -1
  32. data/lib/mocha/object_methods.rb +2 -2
  33. data/lib/mocha/parameter_matchers/base.rb +4 -9
  34. data/lib/mocha/parameter_matchers/equivalent_uri.rb +1 -2
  35. data/lib/mocha/parameter_matchers/has_entries.rb +7 -2
  36. data/lib/mocha/parameter_matchers/includes.rb +3 -3
  37. data/lib/mocha/parameter_matchers/instance_methods.rb +10 -2
  38. data/lib/mocha/parameter_matchers/positional_or_keyword_hash.rb +66 -0
  39. data/lib/mocha/parameter_matchers/responds_with.rb +32 -5
  40. data/lib/mocha/parameters_matcher.rb +10 -6
  41. data/lib/mocha/ruby_version.rb +2 -9
  42. data/lib/mocha/stubbed_method.rb +3 -39
  43. data/lib/mocha/test_unit.rb +1 -4
  44. data/lib/mocha/version.rb +1 -1
  45. data/mocha.gemspec +11 -1
  46. metadata +31 -31
  47. data/init.rb +0 -1
  48. data/lib/mocha/integration/mini_test/nothing.rb +0 -19
  49. data/lib/mocha/integration/mini_test/version_13.rb +0 -54
  50. data/lib/mocha/integration/mini_test/version_140.rb +0 -54
  51. data/lib/mocha/integration/mini_test/version_141.rb +0 -65
  52. data/lib/mocha/integration/mini_test/version_142_to_172.rb +0 -65
  53. data/lib/mocha/integration/mini_test/version_200.rb +0 -66
  54. data/lib/mocha/integration/mini_test/version_201_to_222.rb +0 -66
  55. data/lib/mocha/integration/mini_test/version_2110_to_2111.rb +0 -70
  56. data/lib/mocha/integration/mini_test/version_2112_to_320.rb +0 -73
  57. data/lib/mocha/integration/mini_test/version_230_to_2101.rb +0 -68
  58. data/lib/mocha/integration/mini_test.rb +0 -56
  59. data/lib/mocha/integration/test_unit/gem_version_200.rb +0 -62
  60. data/lib/mocha/integration/test_unit/gem_version_201_to_202.rb +0 -62
  61. data/lib/mocha/integration/test_unit/gem_version_203_to_220.rb +0 -62
  62. data/lib/mocha/integration/test_unit/gem_version_230_to_250.rb +0 -68
  63. data/lib/mocha/integration/test_unit/nothing.rb +0 -19
  64. data/lib/mocha/integration/test_unit/ruby_version_186_and_above.rb +0 -63
  65. data/lib/mocha/integration.rb +0 -11
  66. data/lib/mocha/setup.rb +0 -14
  67. data/yard-templates/default/layout/html/google_analytics.erb +0 -8
  68. data/yard-templates/default/layout/html/setup.rb +0 -5
data/lib/mocha/mock.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'ruby2_keywords'
1
2
  require 'mocha/expectation'
2
3
  require 'mocha/expectation_list'
3
4
  require 'mocha/invocation'
@@ -7,6 +8,7 @@ require 'mocha/method_matcher'
7
8
  require 'mocha/parameters_matcher'
8
9
  require 'mocha/argument_iterator'
9
10
  require 'mocha/expectation_error_factory'
11
+ require 'mocha/deprecation'
10
12
 
11
13
  module Mocha
12
14
  # Traditional mock object.
@@ -33,6 +35,9 @@ module Mocha
33
35
  # while an +expects(:foo).at_least_once+ expectation will always be matched
34
36
  # against invocations.
35
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
+ #
36
41
  # This scheme allows you to:
37
42
  #
38
43
  # - Set up default stubs in your the +setup+ method of your test class and
@@ -99,7 +104,7 @@ module Mocha
99
104
  #
100
105
  # @example Setup multiple expectations using +expected_methods_vs_return_values+.
101
106
  # object = mock()
102
- # 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)
103
108
  #
104
109
  # # is exactly equivalent to
105
110
  #
@@ -113,6 +118,7 @@ module Mocha
113
118
  method_name = args.shift
114
119
  ensure_method_not_already_defined(method_name)
115
120
  expectation = Expectation.new(self, method_name, backtrace)
121
+ expectation.in_sequence(@mockery.sequences.last) if @mockery.sequences.any?
116
122
  expectation.returns(args.shift) unless args.empty?
117
123
  @expectations.add(expectation)
118
124
  end
@@ -137,7 +143,7 @@ module Mocha
137
143
  #
138
144
  # @example Setup multiple expectations using +stubbed_methods_vs_return_values+.
139
145
  # object = mock()
140
- # 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)
141
147
  #
142
148
  # # is exactly equivalent to
143
149
  #
@@ -152,6 +158,7 @@ module Mocha
152
158
  ensure_method_not_already_defined(method_name)
153
159
  expectation = Expectation.new(self, method_name, backtrace)
154
160
  expectation.at_least(0)
161
+ expectation.in_sequence(@mockery.sequences.last) if @mockery.sequences.any?
155
162
  expectation.returns(args.shift) unless args.empty?
156
163
  @expectations.add(expectation)
157
164
  end
@@ -311,16 +318,30 @@ module Mocha
311
318
  def method_missing(symbol, *arguments, &block) # rubocop:disable Style/MethodMissingSuper
312
319
  handle_method_call(symbol, arguments, block)
313
320
  end
321
+ ruby2_keywords(:method_missing)
314
322
 
315
323
  # @private
316
- def handle_method_call(symbol, arguments, block)
324
+ def handle_method_call(symbol, arguments, block) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
317
325
  check_expiry
318
326
  check_responder_responds_to(symbol)
319
327
  invocation = Invocation.new(self, symbol, arguments, block)
320
- if (matching_expectation_allowing_invocation = all_expectations.match_allowing_invocation(invocation))
321
- matching_expectation_allowing_invocation.invoke(invocation)
322
- elsif (matching_expectation = all_expectations.match(invocation)) || (!matching_expectation && !@everything_stubbed)
323
- 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)
324
345
  end
325
346
  end
326
347
 
@@ -339,8 +360,8 @@ module Mocha
339
360
  end
340
361
 
341
362
  # @private
342
- def __expire__
343
- @expired = true
363
+ def __expire__(origin)
364
+ @expired = origin || true
344
365
  end
345
366
 
346
367
  # @private
@@ -369,7 +390,11 @@ module Mocha
369
390
  if @unexpected_invocation.nil?
370
391
  @unexpected_invocation = invocation
371
392
  matching_expectation.invoke(invocation) if matching_expectation
372
- 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}"
373
398
  else
374
399
  message = @unexpected_invocation.short_call_description
375
400
  end
@@ -383,14 +408,16 @@ module Mocha
383
408
  end
384
409
 
385
410
  def check_expiry
386
- if @expired # rubocop:disable Style/GuardClause
387
- Deprecation.warning(
388
- "#{mocha_inspect} was instantiated in one test but it is receiving invocations within another test.",
389
- ' This can lead to unintended interactions between tests and hence unexpected test failures.',
390
- ' Ensure that every test correctly cleans up any state that it introduces.',
391
- ' A Mocha::StubbingError will be raised in this scenario in the future.'
392
- )
393
- end
411
+ return unless @expired
412
+
413
+ origin = @expired == true ? 'one test' : @expired
414
+
415
+ sentences = [
416
+ "#{mocha_inspect} was instantiated in #{origin} but it is receiving invocations within another test.",
417
+ 'This can lead to unintended interactions between tests and hence unexpected test failures.',
418
+ 'Ensure that every test correctly cleans up any state that it introduces.'
419
+ ]
420
+ raise StubbingError.new(sentences.join(' '), caller)
394
421
  end
395
422
  end
396
423
  end
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)
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
@@ -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
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))
@@ -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
@@ -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,12 +1,20 @@
1
+ require 'mocha/parameter_matchers/base'
1
2
  require 'mocha/parameter_matchers/equals'
3
+ require 'mocha/parameter_matchers/positional_or_keyword_hash'
2
4
 
3
5
  module Mocha
4
6
  module ParameterMatchers
5
7
  # @private
6
8
  module InstanceMethods
7
9
  # @private
8
- def to_matcher
9
- 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
10
18
  end
11
19
  end
12
20
  end
@@ -0,0 +1,66 @@
1
+ require 'mocha/configuration'
2
+ require 'mocha/deprecation'
3
+ require 'mocha/parameter_matchers/base'
4
+ require 'mocha/parameter_matchers/has_entries'
5
+
6
+ module Mocha
7
+ module ParameterMatchers
8
+ # @private
9
+ class PositionalOrKeywordHash < Base
10
+ def initialize(value, expectation)
11
+ @value = value
12
+ @expectation = expectation
13
+ end
14
+
15
+ def matches?(available_parameters)
16
+ parameter, is_last_parameter = extract_parameter(available_parameters)
17
+
18
+ return false unless HasEntries.new(@value, exact: true).matches?([parameter])
19
+
20
+ if is_last_parameter && !same_type_of_hash?(parameter, @value)
21
+ return false if Mocha.configuration.strict_keyword_argument_matching?
22
+
23
+ deprecation_warning(parameter, @value) if Mocha::RUBY_V27_PLUS
24
+ end
25
+
26
+ true
27
+ end
28
+
29
+ def mocha_inspect
30
+ @value.mocha_inspect
31
+ end
32
+
33
+ private
34
+
35
+ def extract_parameter(available_parameters)
36
+ [available_parameters.shift, available_parameters.empty?]
37
+ end
38
+
39
+ def same_type_of_hash?(actual, expected)
40
+ ruby2_keywords_hash?(actual) == ruby2_keywords_hash?(expected)
41
+ end
42
+
43
+ def deprecation_warning(actual, expected)
44
+ details1 = "Expectation #{expectation_definition} expected #{hash_type(expected)} (#{expected.mocha_inspect}),".squeeze(' ')
45
+ details2 = "but received #{hash_type(actual)} (#{actual.mocha_inspect})."
46
+ sentence1 = 'These will stop matching when strict keyword argument matching is enabled.'
47
+ sentence2 = 'See the documentation for Mocha::Configuration#strict_keyword_argument_matching=.'
48
+ Deprecation.warning([details1, details2, sentence1, sentence2].join(' '))
49
+ end
50
+
51
+ def hash_type(hash)
52
+ ruby2_keywords_hash?(hash) ? 'keyword arguments' : 'positional hash'
53
+ end
54
+
55
+ def ruby2_keywords_hash?(hash)
56
+ hash.is_a?(Hash) && ::Hash.ruby2_keywords_hash?(hash)
57
+ end
58
+
59
+ def expectation_definition
60
+ return nil unless @expectation
61
+
62
+ "defined at #{@expectation.definition_location}"
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,12 +1,18 @@
1
1
  require 'mocha/parameter_matchers/base'
2
+ require 'mocha/parameter_matchers/all_of'
2
3
  require 'yaml'
3
4
 
4
5
  module Mocha
5
6
  module ParameterMatchers
6
- # Matches any object that responds to +message+ with +result+. To put it another way, it tests the quack, not the duck.
7
+ # @overload def responds_with(message, result)
8
+ # Matches any object that responds to +message+ with +result+. To put it another way, it tests the quack, not the duck.
9
+ # @param [Symbol] message method to invoke.
10
+ # @param [Object] result expected result of sending +message+.
11
+ # @overload def responds_with(messages_vs_results)
12
+ # Matches any object that responds to all the messages with the corresponding results as specified by +messages_vs_results+.
13
+ # @param [Hash<Symbol,Object>] messages_vs_results +Hash+ of messages vs results.
14
+ # @raise [ArgumentError] if +messages_vs_results+ does not contain at least one entry.
7
15
  #
8
- # @param [Symbol] message method to invoke.
9
- # @param [Object] result expected result of sending +message+.
10
16
  # @return [RespondsWith] parameter matcher.
11
17
  #
12
18
  # @see Expectation#with
@@ -22,8 +28,29 @@ module Mocha
22
28
  # object.expects(:method_1).with(responds_with(:upcase, "BAR"))
23
29
  # object.method_1("foo")
24
30
  # # error raised, because "foo".upcase != "BAR"
25
- def responds_with(message, result)
26
- RespondsWith.new(message, result)
31
+ #
32
+ # @example Actual parameter responds with "FOO" when :upcase is invoked and "oof" when :reverse is invoked.
33
+ # object = mock()
34
+ # object.expects(:method_1).with(responds_with(upcase: "FOO", reverse: "oof"))
35
+ # object.method_1("foo")
36
+ # # no error raised, because "foo".upcase == "FOO" and "foo".reverse == "oof"
37
+ def responds_with(*options)
38
+ case options.length
39
+ when 0
40
+ raise ArgumentError, 'No arguments. Expecting at least one.'
41
+ when 1
42
+ option = options.first
43
+ raise ArgumentError, 'Argument is not a Hash.' unless option.is_a?(Hash)
44
+ raise ArgumentError, 'Argument has no entries.' if option.empty?
45
+
46
+ matchers = option.map { |message, result| RespondsWith.new(message, result) }
47
+ AllOf.new(*matchers)
48
+ when 2
49
+ message, result = options
50
+ RespondsWith.new(message, result)
51
+ else
52
+ raise ArgumentError, 'Too many arguments; use either a single argument (must be a Hash) or two arguments (a message and a result).'
53
+ end
27
54
  end
28
55
 
29
56
  # Parameter matcher which matches if actual parameter returns expected result when specified method is invoked.
@@ -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
 
@@ -21,14 +22,17 @@ module Mocha
21
22
  end
22
23
 
23
24
  def mocha_inspect
24
- signature = matchers.mocha_inspect
25
- signature = signature.gsub(/^\[|\]$/, '')
26
- signature = signature.gsub(/^\{|\}$/, '') if matchers.length == 1
27
- "(#{signature})"
25
+ if @matching_block
26
+ '(arguments_accepted_by_custom_matching_block)'
27
+ else
28
+ signature = matchers.mocha_inspect
29
+ signature = signature.gsub(/^\[|\]$/, '')
30
+ "(#{signature})"
31
+ end
28
32
  end
29
33
 
30
34
  def matchers
31
- @expected_parameters.map(&:to_matcher)
35
+ @expected_parameters.map { |p| p.to_matcher(expectation: @expectation, top_level: true) }
32
36
  end
33
37
  end
34
38
  end
@@ -1,11 +1,4 @@
1
- require 'mocha/deprecation'
2
-
3
1
  module Mocha
4
- RUBY_V2_PLUS = Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2')
5
-
6
- unless RUBY_V2_PLUS
7
- Mocha::Deprecation.warning(
8
- 'Versions of Ruby earlier than v2.0 will not be supported in future versions of Mocha.'
9
- )
10
- end
2
+ RUBY_V27_PLUS = Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.7')
3
+ RUBY_V34_PLUS = Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('3.4')
11
4
  end
@@ -1,3 +1,4 @@
1
+ require 'ruby2_keywords'
1
2
  require 'mocha/ruby_version'
2
3
 
3
4
  module Mocha
@@ -20,7 +21,6 @@ module Mocha
20
21
 
21
22
  def unstub
22
23
  remove_new_method
23
- restore_original_method
24
24
  mock.unstub(method_name.to_sym)
25
25
  return if mock.any_expectations?
26
26
  reset_mocha
@@ -37,20 +37,7 @@ module Mocha
37
37
  def hide_original_method
38
38
  return unless original_method_owner.__method_exists__?(method_name)
39
39
  store_original_method_visibility
40
- if use_prepended_module_for_stub_method?
41
- use_prepended_module_for_stub_method
42
- else
43
- begin
44
- store_original_method
45
- # rubocop:disable Lint/HandleExceptions
46
- rescue NameError
47
- # deal with nasties like ActiveRecord::Associations::AssociationProxy
48
- end
49
- # rubocop:enable Lint/HandleExceptions
50
- if stub_method_overwrites_original_method?
51
- remove_original_method_from_stubbee
52
- end
53
- end
40
+ use_prepended_module_for_stub_method
54
41
  end
55
42
 
56
43
  def define_new_method
@@ -59,6 +46,7 @@ module Mocha
59
46
  stub_method_owner.send(:define_method, method_name) do |*args, &block|
60
47
  self_in_scope.mock.handle_method_call(method_name_in_scope, args, block)
61
48
  end
49
+ stub_method_owner.send(:ruby2_keywords, method_name)
62
50
  retain_original_visibility(stub_method_owner)
63
51
  end
64
52
 
@@ -66,18 +54,6 @@ module Mocha
66
54
  stub_method_owner.send(:remove_method, method_name)
67
55
  end
68
56
 
69
- def store_original_method
70
- @original_method = stubbee_method(method_name)
71
- end
72
-
73
- def restore_original_method
74
- return if use_prepended_module_for_stub_method?
75
- if stub_method_overwrites_original_method?
76
- original_method_owner.send(:define_method, method_name, @original_method)
77
- end
78
- retain_original_visibility(original_method_owner)
79
- end
80
-
81
57
  def matches?(other)
82
58
  return false unless other.class == self.class
83
59
  (stubbee.object_id == other.stubbee.object_id) && (method_name == other.method_name)
@@ -100,18 +76,6 @@ module Mocha
100
76
  @original_visibility = original_method_owner.__method_visibility__(method_name)
101
77
  end
102
78
 
103
- def stub_method_overwrites_original_method?
104
- @original_method && @original_method.owner == original_method_owner
105
- end
106
-
107
- def remove_original_method_from_stubbee
108
- original_method_owner.send(:remove_method, method_name)
109
- end
110
-
111
- def use_prepended_module_for_stub_method?
112
- RUBY_V2_PLUS
113
- end
114
-
115
79
  def use_prepended_module_for_stub_method
116
80
  @stub_method_owner = PrependedModule.new
117
81
  original_method_owner.__send__ :prepend, @stub_method_owner
@@ -1,9 +1,6 @@
1
1
  require 'mocha/ruby_version'
2
2
  require 'mocha/integration/test_unit'
3
- require 'mocha/deprecation'
4
3
 
5
4
  unless Mocha::Integration::TestUnit.activate
6
- Mocha::Deprecation.warning(
7
- "Test::Unit must be loaded *before* `require 'mocha/test_unit'`."
8
- )
5
+ raise "Test::Unit must be loaded *before* `require 'mocha/test_unit'`."
9
6
  end
data/lib/mocha/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mocha
2
- VERSION = '1.16.1'.freeze
2
+ VERSION = '2.7.1'.freeze
3
3
  end
data/mocha.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
6
6
  s.name = 'mocha'
7
7
  s.version = Mocha::VERSION
8
8
  s.licenses = ['MIT', 'BSD-2-Clause']
9
- s.required_ruby_version = '>= 1.9'
9
+ s.required_ruby_version = '>= 2.1'
10
10
 
11
11
  s.authors = ['James Mead']
12
12
  s.description = 'Mocking and stubbing library with JMock/SchMock syntax, which allows mocking and stubbing of methods on real (non-mock) classes.'
@@ -21,4 +21,14 @@ Gem::Specification.new do |s|
21
21
  s.homepage = 'https://mocha.jamesmead.org'
22
22
  s.require_paths = ['lib']
23
23
  s.summary = 'Mocking and stubbing library'
24
+ s.metadata = {
25
+ 'bug_tracker_uri' => 'https://github.com/freerange/mocha/issues',
26
+ 'changelog_uri' => 'https://github.com/freerange/mocha/blob/main/RELEASE.md',
27
+ 'documentation_uri' => 'https://mocha.jamesmead.org/',
28
+ 'funding_uri' => 'https://github.com/sponsors/floehopper',
29
+ 'homepage_uri' => s.homepage,
30
+ 'source_code_uri' => 'https://github.com/freerange/mocha'
31
+ }
32
+
33
+ s.add_runtime_dependency 'ruby2_keywords', '>= 0.0.5'
24
34
  end