mocha 1.16.1 → 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 (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