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/Rakefile CHANGED
@@ -5,9 +5,14 @@ end
5
5
  require 'bundler/setup'
6
6
 
7
7
  require 'rake/testtask'
8
+ begin
9
+ # Only available with default Gemfile and in Ruby >= v2.2
10
+ require 'rubocop/rake_task'
11
+ rescue LoadError # rubocop:disable Lint/HandleExceptions
12
+ end
8
13
 
9
- desc 'Run all tests'
10
- task 'default' => ['test', 'test:performance']
14
+ desc 'Run all linters and tests'
15
+ task 'default' => ['lint', 'test', 'test:performance']
11
16
 
12
17
  desc 'Run tests'
13
18
  task 'test' do
@@ -37,10 +42,10 @@ namespace 'test' do # rubocop:disable Metrics/BlockLength
37
42
  end
38
43
 
39
44
  namespace 'integration' do
40
- desc 'Run MiniTest integration tests (intended to be run in its own process)'
45
+ desc 'Run Minitest integration tests (intended to be run in its own process)'
41
46
  Rake::TestTask.new('minitest') do |t|
42
47
  t.libs << 'test'
43
- t.test_files = FileList['test/integration/mini_test_test.rb']
48
+ t.test_files = FileList['test/integration/minitest_test.rb']
44
49
  t.verbose = true
45
50
  t.warning = true
46
51
  end
@@ -76,37 +81,37 @@ namespace 'test' do # rubocop:disable Metrics/BlockLength
76
81
  end
77
82
  end
78
83
 
79
- begin
80
- require 'rubocop/rake_task'
81
- if RUBY_VERSION >= '2.2.0' && (defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby') && ENV['MOCHA_RUN_INTEGRATION_TESTS'].nil?
84
+ task 'lint' do
85
+ if defined?(RuboCop::RakeTask)
82
86
  RuboCop::RakeTask.new
83
- task 'test' => 'rubocop'
87
+ Rake::Task['rubocop'].invoke
88
+ else
89
+ puts 'RuboCop not available - skipping linting'
84
90
  end
85
- rescue LoadError # rubocop:disable Lint/HandleExceptions
86
91
  end
87
92
 
88
93
  # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
89
94
  def benchmark_test_case(klass, iterations)
90
95
  require 'benchmark'
91
- require 'mocha/detection/mini_test'
96
+ require 'mocha/detection/minitest'
92
97
 
93
- if defined?(MiniTest)
94
- minitest_version = Gem::Version.new(Mocha::Detection::MiniTest.version)
98
+ if defined?(Minitest)
99
+ minitest_version = Gem::Version.new(Mocha::Detection::Minitest.version)
95
100
  if Gem::Requirement.new('>= 5.0.0').satisfied_by?(minitest_version)
96
101
  Minitest.seed = 1
97
- result = Benchmark.realtime { iterations.times { |_i| klass.run(MiniTest::CompositeReporter.new) } }
98
- MiniTest::Runnable.runnables.delete(klass)
102
+ result = Benchmark.realtime { iterations.times { |_i| klass.run(Minitest::CompositeReporter.new) } }
103
+ Minitest::Runnable.runnables.delete(klass)
99
104
  result
100
105
  else
101
- MiniTest::Unit.output = StringIO.new
102
- Benchmark.realtime { iterations.times { |_i| MiniTest::Unit.new.run([klass]) } }
106
+ Minitest::Unit.output = StringIO.new
107
+ Benchmark.realtime { iterations.times { |_i| Minitest::Unit.new.run([klass]) } }
103
108
  end
104
109
  else
105
110
  load 'test/unit/ui/console/testrunner.rb' unless defined?(Test::Unit::UI::Console::TestRunner)
106
111
  unless @silent_option
107
112
  begin
108
113
  load 'test/unit/ui/console/outputlevel.rb' unless defined?(Test::Unit::UI::Console::OutputLevel::SILENT)
109
- @silent_option = { :output_level => Test::Unit::UI::Console::OutputLevel::SILENT }
114
+ @silent_option = { output_level: Test::Unit::UI::Console::OutputLevel::SILENT }
110
115
  rescue LoadError
111
116
  @silent_option = Test::Unit::UI::SILENT
112
117
  end
@@ -124,14 +129,8 @@ if ENV['MOCHA_GENERATE_DOCS']
124
129
  `rm -rf ./docs`
125
130
  end
126
131
 
127
- task 'docs_environment' do
128
- unless ENV['GOOGLE_ANALYTICS_WEB_PROPERTY_ID']
129
- puts "\nWarning: GOOGLE_ANALYTICS_WEB_PROPERTY_ID was not defined\n\n"
130
- end
131
- end
132
-
133
132
  desc 'Generate documentation'
134
- YARD::Rake::YardocTask.new('yardoc' => 'docs_environment') do |task|
133
+ YARD::Rake::YardocTask.new('yardoc') do |task|
135
134
  task.options = ['--title', "Mocha #{Mocha::VERSION}", '--fail-on-warning']
136
135
  end
137
136
 
data/lib/mocha/api.rb CHANGED
@@ -7,7 +7,7 @@ require 'mocha/object_methods'
7
7
  require 'mocha/class_methods'
8
8
 
9
9
  module Mocha
10
- # Methods added to +Test::Unit::TestCase+, +MiniTest::Unit::TestCase+ or equivalent.
10
+ # Methods added to +Test::Unit::TestCase+, +Minitest::Unit::TestCase+ or equivalent.
11
11
  # The mock creation methods are {#mock}, {#stub} and {#stub_everything}, all of which return a #{Mock}
12
12
  # which can be further modified by {Mock#responds_like} and {Mock#responds_like_instance_of} methods,
13
13
  # both of which return a {Mock}, too, and can therefore, be chained to the original creation methods.
@@ -52,7 +52,6 @@ module Mocha
52
52
  #
53
53
  # @overload def mock(name)
54
54
  # @param [String, Symbol] name identifies mock object in error messages.
55
- # @note Prior to v1.10.0 when +name+ was a +Symbol+, this method returned an unnamed +Mock+ that expected the method identified by +name+. This was undocumented behaviour and it will be removed in the future, but for the moment it can be reinstated using {Configuration#reinstate_undocumented_behaviour_from_v1_9=}.
56
55
  # @overload def mock(expected_methods_vs_return_values = {})
57
56
  # @param [Hash] expected_methods_vs_return_values expected method name symbols as keys and corresponding return values as values - these expectations are setup as if {Mock#expects} were called multiple times.
58
57
  # @overload def mock(name, expected_methods_vs_return_values = {})
@@ -61,32 +60,14 @@ module Mocha
61
60
  #
62
61
  # @example Using expected_methods_vs_return_values Hash to setup expectations.
63
62
  # def test_motor_starts_and_stops
64
- # motor = mock('motor', :start => true, :stop => true)
63
+ # motor = mock('motor', start: true, stop: true)
65
64
  # assert motor.start
66
65
  # assert motor.stop
67
66
  # # an error will be raised unless both Motor#start and Motor#stop have been called
68
67
  # end
69
68
  #
70
- def mock(*arguments) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
71
- if Mocha.configuration.reinstate_undocumented_behaviour_from_v1_9?
72
- if arguments.first.is_a?(Symbol)
73
- method_name = arguments[0]
74
- Deprecation.warning(
75
- "Explicitly include `#{method_name}` in Hash of expected methods vs return values,",
76
- " e.g. `mock(:#{method_name} => nil)`."
77
- )
78
- if arguments[1]
79
- Deprecation.warning(
80
- "In this case the 2nd argument for `mock(:##{method_name}, ...)` is ignored,",
81
- ' but in the future a Hash of expected methods vs return values will be respected.'
82
- )
83
- end
84
- elsif arguments.first.is_a?(String)
85
- name = arguments.shift
86
- end
87
- elsif arguments.first.is_a?(String) || arguments.first.is_a?(Symbol)
88
- name = arguments.shift
89
- end
69
+ def mock(*arguments)
70
+ name = arguments.shift.to_s if arguments.first.is_a?(String) || arguments.first.is_a?(Symbol)
90
71
  expectations = arguments.shift || {}
91
72
  mock = name ? Mockery.instance.named_mock(name) : Mockery.instance.unnamed_mock
92
73
  mock.expects(expectations)
@@ -99,46 +80,26 @@ module Mocha
99
80
  #
100
81
  # @overload def stub(name)
101
82
  # @param [String, Symbol] name identifies mock object in error messages.
102
- # @note Prior to v1.10.0 when +name+ was a +Symbol+, this method returned an unnamed +Mock+ that stubbed the method identified by +name+. This was undocumented behaviour and it will be removed in the future, but for the moment it can be reinstated using {Configuration#reinstate_undocumented_behaviour_from_v1_9=}.
103
83
  # @overload def stub(stubbed_methods_vs_return_values = {})
104
84
  # @param [Hash] stubbed_methods_vs_return_values stubbed method name symbols as keys and corresponding return values as values - these stubbed methods are setup as if {Mock#stubs} were called multiple times.
105
85
  # @overload def stub(name, stubbed_methods_vs_return_values = {})
106
86
  # @param [String, Symbol] name identifies mock object in error messages.
87
+ # @param [Hash] stubbed_methods_vs_return_values stubbed method name symbols as keys and corresponding return values as values - these stubbed methods are setup as if {Mock#stubs} were called multiple times.
107
88
  #
108
89
  # @example Using stubbed_methods_vs_return_values Hash to setup stubbed methods.
109
90
  # def test_motor_starts_and_stops
110
- # motor = stub('motor', :start => true, :stop => true)
91
+ # motor = stub('motor', start: true, stop: true)
111
92
  # assert motor.start
112
93
  # assert motor.stop
113
94
  # # an error will not be raised even if either Motor#start or Motor#stop has not been called
114
95
  # end
115
- # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
116
96
  def stub(*arguments)
117
- if Mocha.configuration.reinstate_undocumented_behaviour_from_v1_9?
118
- if arguments.first.is_a?(Symbol)
119
- method_name = arguments[0]
120
- Deprecation.warning(
121
- "Explicitly include `#{method_name}` in Hash of stubbed methods vs return values,",
122
- " e.g. `stub(:#{method_name} => nil)`."
123
- )
124
- if arguments[1]
125
- Deprecation.warning(
126
- "In this case the 2nd argument for `stub(:##{method_name}, ...)` is ignored,",
127
- ' but in the future a Hash of stubbed methods vs return values will be respected.'
128
- )
129
- end
130
- elsif arguments.first.is_a?(String)
131
- name = arguments.shift
132
- end
133
- elsif arguments.first.is_a?(String) || arguments.first.is_a?(Symbol)
134
- name = arguments.shift
135
- end
97
+ name = arguments.shift.to_s if arguments.first.is_a?(String) || arguments.first.is_a?(Symbol)
136
98
  expectations = arguments.shift || {}
137
99
  stub = name ? Mockery.instance.named_mock(name) : Mockery.instance.unnamed_mock
138
100
  stub.stubs(expectations)
139
101
  stub
140
102
  end
141
- # rubocop:enable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
142
103
 
143
104
  # Builds a mock object that accepts calls to any method. By default it will return +nil+ for any method call.
144
105
  #
@@ -146,7 +107,6 @@ module Mocha
146
107
  #
147
108
  # @overload def stub_everything(name)
148
109
  # @param [String, Symbol] name identifies mock object in error messages.
149
- # @note Prior to v1.10.0 when +name+ was a +Symbol+, this method returned an unnamed +Mock+ that stubbed the method identified by +name+. This was undocumented behaviour and it will be removed in the future, but for the moment it can be reinstated using {Configuration#reinstate_undocumented_behaviour_from_v1_9=}.
150
110
  # @overload def stub_everything(stubbed_methods_vs_return_values = {})
151
111
  # @param [Hash] stubbed_methods_vs_return_values stubbed method name symbols as keys and corresponding return values as values - these stubbed methods are setup as if {Mock#stubs} were called multiple times.
152
112
  # @overload def stub_everything(name, stubbed_methods_vs_return_values = {})
@@ -155,44 +115,27 @@ module Mocha
155
115
  #
156
116
  # @example Ignore invocations of irrelevant methods.
157
117
  # def test_motor_stops
158
- # motor = stub_everything('motor', :stop => true)
118
+ # motor = stub_everything('motor', stop: true)
159
119
  # assert_nil motor.irrelevant_method_1 # => no error raised
160
120
  # assert_nil motor.irrelevant_method_2 # => no error raised
161
121
  # assert motor.stop
162
122
  # end
163
- # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
164
123
  def stub_everything(*arguments)
165
- if Mocha.configuration.reinstate_undocumented_behaviour_from_v1_9?
166
- if arguments.first.is_a?(Symbol)
167
- method_name = arguments[0]
168
- Deprecation.warning(
169
- "Explicitly include `#{method_name}` in Hash of stubbed methods vs return values,",
170
- " e.g. `stub_everything(:#{method_name} => nil)`."
171
- )
172
- if arguments[1]
173
- Deprecation.warning(
174
- "In this case the 2nd argument for `stub_everything(:##{method_name}, ...)` is ignored,",
175
- ' but in the future a Hash of stubbed methods vs return values will be respected.'
176
- )
177
- end
178
- elsif arguments.first.is_a?(String)
179
- name = arguments.shift
180
- end
181
- elsif arguments.first.is_a?(String) || arguments.first.is_a?(Symbol)
182
- name = arguments.shift
183
- end
124
+ name = arguments.shift if arguments.first.is_a?(String) || arguments.first.is_a?(Symbol)
184
125
  expectations = arguments.shift || {}
185
126
  stub = name ? Mockery.instance.named_mock(name) : Mockery.instance.unnamed_mock
186
127
  stub.stub_everything
187
128
  stub.stubs(expectations)
188
129
  stub
189
130
  end
190
- # rubocop:enable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
191
131
 
192
132
  # Builds a new sequence which can be used to constrain the order in which expectations can occur.
193
133
  #
194
- # Specify that an expected invocation must occur within a named {Sequence} by using {Expectation#in_sequence}.
134
+ # Specify that an expected invocation must occur within a named {Sequence} by calling {Expectation#in_sequence}
135
+ # on each expectation or by passing a block within which all expectations should be constrained by the {Sequence}.
195
136
  #
137
+ # @param [String] name name of sequence
138
+ # @yield optional block within which expectations should be constrained by the sequence
196
139
  # @return [Sequence] a new sequence
197
140
  #
198
141
  # @see Expectation#in_sequence
@@ -216,8 +159,23 @@ module Mocha
216
159
  #
217
160
  # task_one.execute
218
161
  # task_two.execute
162
+ #
163
+ # @example Ensure methods on egg are invoked in the correct order using a block.
164
+ # egg = mock('egg')
165
+ # sequence('breakfast') do
166
+ # egg.expects(:crack)
167
+ # egg.expects(:fry)
168
+ # egg.expects(:eat)
169
+ # end
219
170
  def sequence(name)
220
- Sequence.new(name)
171
+ Sequence.new(name).tap do |seq|
172
+ Mockery.instance.sequences.push(seq)
173
+ begin
174
+ yield if block_given?
175
+ ensure
176
+ Mockery.instance.sequences.pop
177
+ end
178
+ end
221
179
  end
222
180
 
223
181
  # Builds a new state machine which can be used to constrain the order in which expectations can occur.
@@ -230,6 +188,7 @@ module Mocha
230
188
  #
231
189
  # A test can contain multiple state machines.
232
190
  #
191
+ # @param [String] name name of state machine
233
192
  # @return [StateMachine] a new state machine
234
193
  #
235
194
  # @see Expectation#then
@@ -3,11 +3,11 @@ module Mocha
3
3
  LIB_DIRECTORY = File.expand_path(File.join(File.dirname(__FILE__), '..')) + File::SEPARATOR
4
4
 
5
5
  def initialize(lib_directory = LIB_DIRECTORY)
6
- @path_pattern = Regexp.new(lib_directory)
6
+ @lib_directory = lib_directory
7
7
  end
8
8
 
9
9
  def filtered(backtrace)
10
- backtrace.reject { |location| @path_pattern.match(File.expand_path(location)) }
10
+ backtrace.reject { |location| File.expand_path(location).start_with?(@lib_directory) }
11
11
  end
12
12
  end
13
13
  end
@@ -34,6 +34,10 @@ module Mocha
34
34
  @invocations.size < maximum
35
35
  end
36
36
 
37
+ def invocations_never_allowed?
38
+ maximum.zero?
39
+ end
40
+
37
41
  def satisfied?
38
42
  @invocations.size >= required
39
43
  end
@@ -1,3 +1,6 @@
1
+ require 'mocha/ruby_version'
2
+ require 'mocha/deprecation'
3
+
1
4
  module Mocha
2
5
  # Allows setting of configuration options. See {Configuration} for the available options.
3
6
  #
@@ -37,13 +40,13 @@ module Mocha
37
40
  class Configuration
38
41
  # @private
39
42
  DEFAULTS = {
40
- :stubbing_method_unnecessarily => :allow,
41
- :stubbing_method_on_non_mock_object => :allow,
42
- :stubbing_non_existent_method => :allow,
43
- :stubbing_non_public_method => :allow,
44
- :stubbing_method_on_nil => :prevent,
45
- :display_matching_invocations_on_failure => false,
46
- :reinstate_undocumented_behaviour_from_v1_9 => false
43
+ stubbing_method_unnecessarily: :allow,
44
+ stubbing_method_on_non_mock_object: :allow,
45
+ stubbing_non_existent_method: :allow,
46
+ stubbing_non_public_method: :allow,
47
+ stubbing_method_on_nil: :prevent,
48
+ display_matching_invocations_on_failure: false,
49
+ strict_keyword_argument_matching: false
47
50
  }.freeze
48
51
 
49
52
  attr_reader :options
@@ -204,8 +207,14 @@ module Mocha
204
207
  # When +value+ is +:prevent+, raise a {StubbingError}. This is the default.
205
208
  #
206
209
  # @param [Symbol] value one of +:allow+, +:warn+, +:prevent+.
210
+ # @deprecated This method is deprecated and will be removed in a future release. +nil+ is frozen in Ruby >= v2.2 and Mocha will be dropping support for Ruby v2.1. At that point it won't be possible to stub methods on +nil+ any more.
207
211
  #
208
212
  def stubbing_method_on_nil=(value)
213
+ Deprecation.warning([
214
+ '`Mocha::Configuration#stubbing_method_on_nil=` is deprecated and will be removed in a future release.',
215
+ '`nil` is frozen in Ruby >= v2.2 and Mocha will be dropping support for Ruby v2.1.',
216
+ "At that point it won't be possible to stub methods on `nil` any more."
217
+ ].join(' '))
209
218
  @options[:stubbing_method_on_nil] = value
210
219
  end
211
220
 
@@ -248,145 +257,54 @@ module Mocha
248
257
  @options[:display_matching_invocations_on_failure]
249
258
  end
250
259
 
251
- # Reinstate undocumented behaviour from v1.9
252
- #
253
- # Previously when {API#mock}, {API#stub}, or {API#stub_everything} were called with the first argument being a symbol, they built an *unnamed* mock object *and* expected or stubbed the method identified by the symbol argument; subsequent arguments were ignored.
254
- # Now these methods build a *named* mock with the name specified by the symbol argument; *no* methods are expected or stubbed and subsequent arguments *are* taken into account.
260
+ # Perform strict keyword argument comparison. Only supported in Ruby >= v2.7.
255
261
  #
256
- # Previously if {Expectation#yields} or {Expectation#multiple_yields} was called on an expectation, but no block was given when the method was invoked, the instruction to yield was ignored.
257
- # Now a +LocalJumpError+ is raised.
262
+ # When this option is set to +false+ a positional +Hash+ and a set of keyword arguments are treated the same during comparison, which can lead to misleading passing tests in Ruby >= v3.0 (see examples below). However, a deprecation warning will be displayed if a positional +Hash+ matches a set of keyword arguments or vice versa. This is because {#strict_keyword_argument_matching=} will default to +true+ in the future.
258
263
  #
259
- # Enabling this configuration option reinstates the previous behaviour, but displays a deprecation warning.
264
+ # For more details on keyword arguments in Ruby v3, refer to {https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0 this article}.
260
265
  #
261
- # @param [Boolean] value +true+ to reinstate undocumented behaviour; disabled by default.
262
- # @deprecated Fix deprecation warnings caused by reliance on v1.9 behaviour and remove calls to this method.
266
+ # Note that +Hash+-related matchers such as {ParameterMatchers#has_value} or {ParameterMatchers#has_key} will still treat a positional +Hash+ and a set of keyword arguments the same, so misleading passing tests are still possible when they are used.
263
267
  #
264
- # @example Reinstate undocumented behaviour for {API#mock}
265
- # Mocha.configure do |c|
266
- # c.reinstate_undocumented_behaviour_from_v1_9 = true
267
- # end
268
+ # This configuration option is +false+ by default to enable gradual adoption, but will be +true+ by default in the future.
268
269
  #
269
- # foo = mock(:bar)
270
- # foo.inspect # => #<Mock>
270
+ # @param [Boolean] value +true+ to enable strict keyword argument matching; +false+ by default.
271
271
  #
272
- # not all expectations were satisfied
273
- # unsatisfied expectations:
274
- # - expected exactly once, invoked never: #<Mock>.foo
272
+ # @example Loose keyword argument matching (default)
275
273
  #
276
- # @example Reinstate undocumented behaviour for {API#stub}
277
- # Mocha.configure do |c|
278
- # c.reinstate_undocumented_behaviour_from_v1_9 = true
274
+ # class Example
275
+ # def foo(a, bar:); end
279
276
  # end
280
277
  #
281
- # foo = stub(:bar)
282
- # foo.inspect # => #<Mock>
283
- # foo.bar # => nil
278
+ # example = Example.new
279
+ # example.expects(:foo).with('a', bar: 'b')
280
+ # example.foo('a', { bar: 'b' })
281
+ # # This passes the test, but would result in an ArgumentError in practice
284
282
  #
285
- # @example Reinstate undocumented behaviour for {Expectation#yields}
286
- # foo = mock('foo')
287
- # foo.stubs(:my_method).yields(1, 2)
288
- # foo.my_method # => raises LocalJumpError when no block is supplied
283
+ # @example Strict keyword argument matching
289
284
  #
290
285
  # Mocha.configure do |c|
291
- # c.reinstate_undocumented_behaviour_from_v1_9 = true
286
+ # c.strict_keyword_argument_matching = true
292
287
  # end
293
288
  #
294
- # foo = mock('foo')
295
- # foo.stubs(:my_method).yields(1, 2)
296
- # foo.my_method # => does *not* raise LocalJumpError when no block is supplied
297
- #
298
- def reinstate_undocumented_behaviour_from_v1_9=(value)
299
- if value
300
- sentence1 = 'Configuration#reinstate_undocumented_behaviour_from_v1_9= will be removed in the future.'
301
- sentence2 = 'Fix deprecation warnings caused by reliance on v1.9 behaviour.'
302
- sentence3 = 'See docs for API#mock, API#stub, API#stub_everything, Expectation#yields and Expectation#multiple_yields.'
303
- Deprecation.warning([sentence1, sentence2, sentence3].join(' '))
304
- else
305
- sentence1 = 'Configuration#reinstate_undocumented_behaviour_from_v1_9= is unnecessarily being set to false, because this is now the default value.'
306
- sentence2 = 'Configuration#reinstate_undocumented_behaviour_from_v1_9= will be removed in the future, so you should avoid calling it.'
307
- Deprecation.warning([sentence1, sentence2].join(' '))
308
- end
309
- @options[:reinstate_undocumented_behaviour_from_v1_9] = value
289
+ # class Example
290
+ # def foo(a, bar:); end
291
+ # end
292
+ #
293
+ # example = Example.new
294
+ # example.expects(:foo).with('a', bar: 'b')
295
+ # example.foo('a', { bar: 'b' })
296
+ # # This now fails as expected
297
+ def strict_keyword_argument_matching=(value)
298
+ raise 'Strict keyword argument matching requires Ruby 2.7 and above.' unless Mocha::RUBY_V27_PLUS
299
+ @options[:strict_keyword_argument_matching] = value
310
300
  end
311
301
 
312
302
  # @private
313
- def reinstate_undocumented_behaviour_from_v1_9?
314
- @options[:reinstate_undocumented_behaviour_from_v1_9]
303
+ def strict_keyword_argument_matching?
304
+ @options[:strict_keyword_argument_matching]
315
305
  end
316
306
 
317
307
  class << self
318
- # Allow the specified +action+.
319
- #
320
- # @param [Symbol] action one of +:stubbing_method_unnecessarily+, +:stubbing_method_on_non_mock_object+, +:stubbing_non_existent_method+, +:stubbing_non_public_method+, +:stubbing_method_on_nil+.
321
- # @yield optional block during which the configuration change will be changed before being returned to its original value at the end of the block.
322
- # @deprecated If a block is supplied, call {.override} with a +Hash+ containing an entry with the +action+ as the key and +:allow+ as the value. If no block is supplied, call the appropriate +action+ writer method with +value+ set to +:allow+ via {Mocha.configure}. The writer method will be the one of the following corresponding to the +action+:
323
- # * {#stubbing_method_unnecessarily=}
324
- # * {#stubbing_method_on_non_mock_object=}
325
- # * {#stubbing_non_existent_method=}
326
- # * {#stubbing_non_public_method=}
327
- # * {#stubbing_method_on_nil=}
328
- def allow(action, &block)
329
- if block_given?
330
- Deprecation.warning("Use Mocha::Configuration.override(#{action}: :allow) with the same block")
331
- else
332
- Deprecation.warning("Use Mocha.configure { |c| c.#{action} = :allow }")
333
- end
334
- change_config action, :allow, &block
335
- end
336
-
337
- # @private
338
- def allow?(action)
339
- configuration.allow?(action)
340
- end
341
-
342
- # Warn if the specified +action+ is attempted.
343
- #
344
- # @param [Symbol] action one of +:stubbing_method_unnecessarily+, +:stubbing_method_on_non_mock_object+, +:stubbing_non_existent_method+, +:stubbing_non_public_method+, +:stubbing_method_on_nil+.
345
- # @yield optional block during which the configuration change will be changed before being returned to its original value at the end of the block.
346
- # @deprecated If a block is supplied, call {.override} with a +Hash+ containing an entry with the +action+ as the key and +:warn+ as the value. If no block is supplied, call the appropriate +action+ writer method with +value+ set to +:warn+ via {Mocha.configure}. The writer method will be the one of the following corresponding to the +action+:
347
- # * {#stubbing_method_unnecessarily=}
348
- # * {#stubbing_method_on_non_mock_object=}
349
- # * {#stubbing_non_existent_method=}
350
- # * {#stubbing_non_public_method=}
351
- # * {#stubbing_method_on_nil=}
352
- def warn_when(action, &block)
353
- if block_given?
354
- Deprecation.warning("Use Mocha::Configuration.override(#{action}: :warn) with the same block")
355
- else
356
- Deprecation.warning("Use Mocha.configure { |c| c.#{action} = :warn }")
357
- end
358
- change_config action, :warn, &block
359
- end
360
-
361
- # @private
362
- def warn_when?(action)
363
- configuration.warn_when?(action)
364
- end
365
-
366
- # Raise a {StubbingError} if the specified +action+ is attempted.
367
- #
368
- # @param [Symbol] action one of +:stubbing_method_unnecessarily+, +:stubbing_method_on_non_mock_object+, +:stubbing_non_existent_method+, +:stubbing_non_public_method+, +:stubbing_method_on_nil+.
369
- # @yield optional block during which the configuration change will be changed before being returned to its original value at the end of the block.
370
- # @deprecated If a block is supplied, call {.override} with a +Hash+ containing an entry with the +action+ as the key and +:prevent+ as the value. If no block is supplied, call the appropriate +action+ writer method with +value+ set to +:prevent+ via {Mocha.configure}. The writer method will be the one of the following corresponding to the +action+:
371
- # * {#stubbing_method_unnecessarily=}
372
- # * {#stubbing_method_on_non_mock_object=}
373
- # * {#stubbing_non_existent_method=}
374
- # * {#stubbing_non_public_method=}
375
- # * {#stubbing_method_on_nil=}
376
- def prevent(action, &block)
377
- if block_given?
378
- Deprecation.warning("Use Mocha::Configuration.override(#{action}: :prevent) with the same block")
379
- else
380
- Deprecation.warning("Use Mocha.configure { |c| c.#{action} = :prevent }")
381
- end
382
- change_config action, :prevent, &block
383
- end
384
-
385
- # @private
386
- def prevent?(action)
387
- configuration.prevent?(action)
388
- end
389
-
390
308
  # @private
391
309
  def reset_configuration
392
310
  @configuration = nil
data/lib/mocha/debug.rb CHANGED
@@ -1,12 +1,9 @@
1
1
  module Mocha
2
2
  module Debug
3
- OPTIONS = (ENV['MOCHA_OPTIONS'] || '').split(',').inject({}) do |hash, key|
4
- hash[key] = true
5
- hash
6
- end.freeze
3
+ OPTIONS = (ENV['MOCHA_OPTIONS'] || '').split(',').freeze
7
4
 
8
5
  def self.puts(message)
9
- warn(message) if OPTIONS['debug']
6
+ warn(message) if OPTIONS.include?('debug')
10
7
  end
11
8
  end
12
9
  end
@@ -1,17 +1,17 @@
1
1
  module Mocha
2
2
  module Detection
3
- module MiniTest
3
+ module Minitest
4
4
  def self.testcase
5
5
  if defined?(::Minitest::Test)
6
6
  ::Minitest::Test
7
- elsif defined?(::MiniTest::Unit::TestCase)
8
- ::MiniTest::Unit::TestCase
7
+ elsif defined?(::Minitest::Unit::TestCase)
8
+ ::Minitest::Unit::TestCase
9
9
  end
10
10
  end
11
11
 
12
12
  def self.version
13
- if defined?(::MiniTest::Unit::VERSION)
14
- ::MiniTest::Unit::VERSION
13
+ if defined?(::Minitest::Unit::VERSION)
14
+ ::Minitest::Unit::VERSION
15
15
  elsif defined?(::Minitest::VERSION)
16
16
  ::Minitest::VERSION
17
17
  else
@@ -3,8 +3,8 @@ module Mocha
3
3
  module TestUnit
4
4
  def self.testcase
5
5
  if defined?(::Test::Unit::TestCase) &&
6
- !(defined?(::MiniTest::Unit::TestCase) && (::Test::Unit::TestCase < ::MiniTest::Unit::TestCase)) &&
7
- !(defined?(::MiniTest::Spec) && (::Test::Unit::TestCase < ::MiniTest::Spec))
6
+ !(defined?(::Minitest::Unit::TestCase) && (::Test::Unit::TestCase < ::Minitest::Unit::TestCase)) &&
7
+ !(defined?(::Minitest::Spec) && (::Test::Unit::TestCase < ::Minitest::Spec))
8
8
  ::Test::Unit::TestCase
9
9
  end
10
10
  end