mocha 1.16.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -1
  3. data/Gemfile +3 -3
  4. data/README.md +6 -0
  5. data/RELEASE.md +31 -2
  6. data/Rakefile +12 -7
  7. data/lib/mocha/api.rb +5 -65
  8. data/lib/mocha/configuration.rb +31 -120
  9. data/lib/mocha/expectation.rb +62 -7
  10. data/lib/mocha/inspect.rb +2 -2
  11. data/lib/mocha/integration/mini_test.rb +10 -38
  12. data/lib/mocha/integration/test_unit/adapter.rb +1 -1
  13. data/lib/mocha/integration/test_unit.rb +10 -31
  14. data/lib/mocha/invocation.rb +2 -15
  15. data/lib/mocha/minitest.rb +1 -4
  16. data/lib/mocha/mock.rb +21 -17
  17. data/lib/mocha/parameter_matchers/base.rb +1 -1
  18. data/lib/mocha/parameter_matchers/equivalent_uri.rb +0 -1
  19. data/lib/mocha/parameter_matchers/instance_methods.rb +10 -1
  20. data/lib/mocha/parameter_matchers/positional_or_keyword_hash.rb +64 -0
  21. data/lib/mocha/parameters_matcher.rb +3 -3
  22. data/lib/mocha/ruby_version.rb +1 -9
  23. data/lib/mocha/stubbed_method.rb +3 -39
  24. data/lib/mocha/test_unit.rb +1 -4
  25. data/lib/mocha/version.rb +1 -1
  26. data/mocha.gemspec +1 -1
  27. metadata +4 -22
  28. data/init.rb +0 -1
  29. data/lib/mocha/integration/mini_test/nothing.rb +0 -19
  30. data/lib/mocha/integration/mini_test/version_13.rb +0 -54
  31. data/lib/mocha/integration/mini_test/version_140.rb +0 -54
  32. data/lib/mocha/integration/mini_test/version_141.rb +0 -65
  33. data/lib/mocha/integration/mini_test/version_142_to_172.rb +0 -65
  34. data/lib/mocha/integration/mini_test/version_200.rb +0 -66
  35. data/lib/mocha/integration/mini_test/version_201_to_222.rb +0 -66
  36. data/lib/mocha/integration/mini_test/version_2110_to_2111.rb +0 -70
  37. data/lib/mocha/integration/mini_test/version_2112_to_320.rb +0 -73
  38. data/lib/mocha/integration/mini_test/version_230_to_2101.rb +0 -68
  39. data/lib/mocha/integration/test_unit/gem_version_200.rb +0 -62
  40. data/lib/mocha/integration/test_unit/gem_version_201_to_202.rb +0 -62
  41. data/lib/mocha/integration/test_unit/gem_version_203_to_220.rb +0 -62
  42. data/lib/mocha/integration/test_unit/gem_version_230_to_250.rb +0 -68
  43. data/lib/mocha/integration/test_unit/nothing.rb +0 -19
  44. data/lib/mocha/integration/test_unit/ruby_version_186_and_above.rb +0 -63
  45. data/lib/mocha/integration.rb +0 -11
  46. data/lib/mocha/setup.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e91d0f079638506c5d57fc91f1d84f3232e9c9b923b4dcf173697ab564d72a9
4
- data.tar.gz: 7522a45c0fe7808e89b68e9703959fa320c722bf6fa760091e8aae7f9f25237c
3
+ metadata.gz: 4f52376e6c8b1042085cca75e7d22e0241452dca80e73de22510f39a83041030
4
+ data.tar.gz: aa4584255d27f58ee1e9c18935921e352044ac8c2cc91d97221d280e21c54b29
5
5
  SHA512:
6
- metadata.gz: 4ac64c4e5ab1ffca88e410601a3f10e4dda4fde0cb2e00b6975280dcd9410f16918f249d6b98926cfc18956a165ef1e24d0d719d093ff29c9663bdfadb8b6457
7
- data.tar.gz: 4eb4166b55e1bb8ee63e8848db0f64d6368abe6ae03897aa680143e6a046202ec5420cbf2a2d01f2f4d0c893210dccf7277627ce81f3db523fd76c1de4618926
6
+ metadata.gz: d23cc4f2cb0ffcf1464103c1bca607d8a2fac7fef3592dc4637d25ed72dbfd91f5d7234c9f4df23f1c27248829b6638013396e780212f41d9a44236b7a95bd16
7
+ data.tar.gz: efbcf261aab3292625c56f9f8cab796a19b4dc1ac474a64041ee1a6a92e72c4941b1ce0a0299322e682607d8e8c92598da7ffe853de1257cd92f49027b0891d4
data/.rubocop.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
- TargetRubyVersion: 2.2 # closest to required_ruby_version of '>= 1.9'
4
+ TargetRubyVersion: 2.2 # closest to required_ruby_version of '>= 2.0'
5
5
 
6
6
  # Even the reference in the documentation suggests that you should prefer
7
7
  # `alias_method` vs `alias`, so I don't understand why that isn't the default.
@@ -48,6 +48,10 @@ Style/WhileUntilModifier:
48
48
  Style/AccessModifierDeclarations:
49
49
  Enabled: false
50
50
 
51
+ # This is useful when using `ExecutionPoint.current` to make tests more robust
52
+ Style/Semicolon:
53
+ Enabled: false
54
+
51
55
  # Enabling this cop results in an "Infinite loop detected" exception
52
56
  Layout/AccessModifierIndentation:
53
57
  Enabled: false
data/Gemfile CHANGED
@@ -3,9 +3,7 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  # rubocop:disable Bundler/DuplicatedGem
6
- if RUBY_VERSION < '2'
7
- gem 'rake', '~> 12.2.1'
8
- elsif RUBY_VERSION < '2.2'
6
+ if RUBY_VERSION < '2.2'
9
7
  gem 'rake', '~> 12.3.3'
10
8
  else
11
9
  gem 'rake'
@@ -30,3 +28,5 @@ if ENV['MOCHA_GENERATE_DOCS']
30
28
  gem 'redcarpet'
31
29
  gem 'yard'
32
30
  end
31
+
32
+ gem 'ruby2_keywords', '~> 0.0.5'
data/README.md CHANGED
@@ -302,6 +302,12 @@ See the [documentation](https://mocha.jamesmead.org/Mocha/Configuration.html) fo
302
302
  Only the following values are currently recognized and have an effect:
303
303
  * `debug`: Enables a debug mode which will output backtraces for each deprecation warning. This is useful for finding where in the test suite the deprecated calls are.
304
304
 
305
+ ### Semantic versioning
306
+
307
+ * Every effort is made to comply with [semantic versioning](https://semver.org/).
308
+ * However, this only applies to the behaviour documented in the public API.
309
+ * The documented public API does *not* include the content or format of messsages displayed to the user, e.g. assertion failure messages.
310
+
305
311
  ### Useful Links
306
312
 
307
313
  * [Official Documentation](https://mocha.jamesmead.org)
data/RELEASE.md CHANGED
@@ -1,10 +1,39 @@
1
1
  # Release Notes
2
2
 
3
- ## 1.16.1
3
+ ## 2.0.0
4
4
 
5
5
  ### External changes
6
6
 
7
- * Fix regression in `Mock#responds_like` behaviour - thanks to @adrianna-chang-shopify for reporting (#580,#583,77af2af1)
7
+ * Remove support for Ruby v1.9 - thanks to @wasabigeek (#552)
8
+ * Support strict keyword argument matching - see docs for `Expectation#with` & `Configuration#strict_keyword_argument_matching=` - thanks to @wasabigeek (#446,#535,#544,#562)
9
+ * Deprecate `Hash` args that don't strictly match (#563,981c31be)
10
+ * Drop support for older versions of test-unit - gem versions of test-unit earlier than v2.5.1 and versions of test-unit
11
+ from the Ruby v1.8 standard library are no longer supported (#540,969f4845)
12
+ * Drop support for older versions of minitest - versions of minitest earlier than v3.3.0 are no longer supported (#541,ca69dc9e)
13
+ * Remove deprecated `mocha/setup.rb` mechanism (642a0ff4)
14
+ * Add missing docs for `API#stub` parameter (257b4cb4)
15
+ * Remove optional reinstatement of v1.9 behaviour (#436,#438,#569,1473ee25)
16
+ * Remove deprecated methods in `Configuration` (#421,e7ff7528)
17
+ * Fail fast when mock receives invocations in another test (#440,#442,cb054d59)
18
+ * Improve docs re using matchers in `Expectation#with` (da7237cd)
19
+ * Expand `Expectation#with` docs re keyword arguments (fed6808d)
20
+ * Improve docs for `strict_keyword_argument_matching` (8d8f881d)
21
+ * Remove deprecated Rails plugin `init.rb` file (1c617175)
22
+ * Improve strict keyword argument matching deprecation warning by including the source location of the stub definition (77c0d4cc)
23
+ * Add README section re semantic versioning (00758246)
24
+
25
+ ### Internal changes
26
+
27
+ * Separate linting from tests in terms of Rake tasks & CircleCI jobs - thanks to @wasabigeek (#556)
28
+ * Remove tests specific to Ruby v1.8 behaviour (46fca7ac, 3b369e99)
29
+ * Multi-line rubocop disable in `Mock#method_missing` (af2194c4)
30
+ * Remove unused arg for `HashMethods#mocha_inspect` (4f59e27f)
31
+ * Improve test runner assertions - failure vs error (eec7200a)
32
+ * Improve test coverage of `PositionalOrKeywordHash` (c294fe70)
33
+ * More consistent Test::Unit & Minitest integration (27dd3817)
34
+ * Remove redundant `require` statements (d82218a8,fa17b114)
35
+ * Add missing `require` statement (73493761)
36
+ * Disable Style/Semicolon cop globally (8cd0b705)
8
37
 
9
38
  ## 1.16.0
10
39
 
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
@@ -76,13 +81,13 @@ 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
data/lib/mocha/api.rb CHANGED
@@ -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 = {})
@@ -67,26 +66,8 @@ module Mocha
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,11 +80,11 @@ 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
@@ -112,33 +93,13 @@ module Mocha
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 = {})
@@ -160,34 +120,14 @@ module Mocha
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
  #
@@ -1,3 +1,5 @@
1
+ require 'mocha/ruby_version'
2
+
1
3
  module Mocha
2
4
  # Allows setting of configuration options. See {Configuration} for the available options.
3
5
  #
@@ -43,7 +45,7 @@ module Mocha
43
45
  :stubbing_non_public_method => :allow,
44
46
  :stubbing_method_on_nil => :prevent,
45
47
  :display_matching_invocations_on_failure => false,
46
- :reinstate_undocumented_behaviour_from_v1_9 => false
48
+ :strict_keyword_argument_matching => false
47
49
  }.freeze
48
50
 
49
51
  attr_reader :options
@@ -248,145 +250,54 @@ module Mocha
248
250
  @options[:display_matching_invocations_on_failure]
249
251
  end
250
252
 
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.
253
+ # Perform strict keyword argument comparison. Only supported in Ruby >= v2.7.
255
254
  #
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.
255
+ # 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
256
  #
259
- # Enabling this configuration option reinstates the previous behaviour, but displays a deprecation warning.
257
+ # 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
258
  #
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.
259
+ # 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
260
  #
264
- # @example Reinstate undocumented behaviour for {API#mock}
265
- # Mocha.configure do |c|
266
- # c.reinstate_undocumented_behaviour_from_v1_9 = true
267
- # end
261
+ # This configuration option is +false+ by default to enable gradual adoption, but will be +true+ by default in the future.
268
262
  #
269
- # foo = mock(:bar)
270
- # foo.inspect # => #<Mock>
263
+ # @param [Boolean] value +true+ to enable strict keyword argument matching; +false+ by default.
271
264
  #
272
- # not all expectations were satisfied
273
- # unsatisfied expectations:
274
- # - expected exactly once, invoked never: #<Mock>.foo
265
+ # @example Loose keyword argument matching (default)
275
266
  #
276
- # @example Reinstate undocumented behaviour for {API#stub}
277
- # Mocha.configure do |c|
278
- # c.reinstate_undocumented_behaviour_from_v1_9 = true
267
+ # class Example
268
+ # def foo(a, bar:); end
279
269
  # end
280
270
  #
281
- # foo = stub(:bar)
282
- # foo.inspect # => #<Mock>
283
- # foo.bar # => nil
271
+ # example = Example.new
272
+ # example.expects(:foo).with('a', bar: 'b')
273
+ # example.foo('a', { bar: 'b' })
274
+ # # This passes the test, but would result in an ArgumentError in practice
284
275
  #
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
276
+ # @example Strict keyword argument matching
289
277
  #
290
278
  # Mocha.configure do |c|
291
- # c.reinstate_undocumented_behaviour_from_v1_9 = true
279
+ # c.strict_keyword_argument_matching = true
292
280
  # end
293
281
  #
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
282
+ # class Example
283
+ # def foo(a, bar:); end
284
+ # end
285
+ #
286
+ # example = Example.new
287
+ # example.expects(:foo).with('a', bar: 'b')
288
+ # example.foo('a', { bar: 'b' })
289
+ # # This now fails as expected
290
+ def strict_keyword_argument_matching=(value)
291
+ raise 'Strict keyword argument matching requires Ruby 2.7 and above.' unless Mocha::RUBY_V27_PLUS
292
+ @options[:strict_keyword_argument_matching] = value
310
293
  end
311
294
 
312
295
  # @private
313
- def reinstate_undocumented_behaviour_from_v1_9?
314
- @options[:reinstate_undocumented_behaviour_from_v1_9]
296
+ def strict_keyword_argument_matching?
297
+ @options[:strict_keyword_argument_matching]
315
298
  end
316
299
 
317
300
  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
301
  # @private
391
302
  def reset_configuration
392
303
  @configuration = nil
@@ -1,3 +1,4 @@
1
+ require 'ruby2_keywords'
1
2
  require 'mocha/method_matcher'
2
3
  require 'mocha/parameters_matcher'
3
4
  require 'mocha/expectation_error'
@@ -11,6 +12,7 @@ require 'mocha/change_state_side_effect'
11
12
  require 'mocha/cardinality'
12
13
  require 'mocha/configuration'
13
14
  require 'mocha/block_matcher'
15
+ require 'mocha/backtrace_filter'
14
16
 
15
17
  module Mocha
16
18
  # Methods on expectations returned from {Mock#expects}, {Mock#stubs}, {ObjectMethods#expects} and {ObjectMethods#stubs}.
@@ -187,17 +189,26 @@ module Mocha
187
189
  at_most(1)
188
190
  end
189
191
 
190
- # Modifies expectation so that the expected method must be called with +expected_parameters+.
192
+ # Modifies expectation so that the expected method must be called with +expected_parameters_or_matchers+.
191
193
  #
192
- # May be used with parameter matchers in {ParameterMatchers}.
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.
193
195
  #
194
- # @param [*Array] expected_parameters parameters expected.
196
+ # 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
+ #
198
+ # 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.
199
+ #
200
+ # When {Configuration#strict_keyword_argument_matching=} is set to +true+, an actual positional +Hash+ will not match an expected set of keyword arguments; and vice versa, an actual set of keyword arguments will not match an expected positional +Hash+, i.e. the parameter matching is stricter.
201
+ #
202
+ # @see ParameterMatchers
203
+ # @see Configuration#strict_keyword_argument_matching=
204
+ #
205
+ # @param [*Array<Object,ParameterMatchers::Base>] expected_parameters_or_matchers expected parameter values or parameter matchers.
195
206
  # @yield optional block specifying custom matching.
196
- # @yieldparam [*Array] actual_parameters parameters with which expected method was invoked.
207
+ # @yieldparam [*Array<Object>] actual_parameters parameters with which expected method was invoked.
197
208
  # @yieldreturn [Boolean] +true+ if +actual_parameters+ are acceptable.
198
209
  # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
199
210
  #
200
- # @example Expected method must be called with expected parameters.
211
+ # @example Expected method must be called with exact parameter values.
201
212
  # object = mock()
202
213
  # object.expects(:expected_method).with(:param1, :param2)
203
214
  # object.expected_method(:param1, :param2)
@@ -208,6 +219,43 @@ module Mocha
208
219
  # object.expected_method(:param3)
209
220
  # # => verify fails
210
221
  #
222
+ # @example Expected method must be called with parameters matching parameter matchers.
223
+ # object = mock()
224
+ # object.expects(:expected_method).with(includes('string2'), anything)
225
+ # object.expected_method(['string1', 'string2'], 'any-old-value')
226
+ # # => verify succeeds
227
+ #
228
+ # object = mock()
229
+ # object.expects(:expected_method).with(includes('string2'), anything)
230
+ # object.expected_method(['string1'], 'any-old-value')
231
+ # # => verify fails
232
+ #
233
+ # @example Loose keyword argument matching (default)
234
+ #
235
+ # class Example
236
+ # def foo(a, bar:); end
237
+ # end
238
+ #
239
+ # example = Example.new
240
+ # example.expects(:foo).with('a', bar: 'b')
241
+ # example.foo('a', { bar: 'b' })
242
+ # # This passes the test, but would result in an ArgumentError in practice
243
+ #
244
+ # @example Strict keyword argument matching
245
+ #
246
+ # Mocha.configure do |c|
247
+ # c.strict_keyword_argument_matching = true
248
+ # end
249
+ #
250
+ # class Example
251
+ # def foo(a, bar:); end
252
+ # end
253
+ #
254
+ # example = Example.new
255
+ # example.expects(:foo).with('a', bar: 'b')
256
+ # example.foo('a', { bar: 'b' })
257
+ # # This now fails as expected
258
+ #
211
259
  # @example Expected method must be called with a value divisible by 4.
212
260
  # object = mock()
213
261
  # object.expects(:expected_method).with() { |value| value % 4 == 0 }
@@ -218,10 +266,11 @@ module Mocha
218
266
  # object.expects(:expected_method).with() { |value| value % 4 == 0 }
219
267
  # object.expected_method(17)
220
268
  # # => verify fails
221
- def with(*expected_parameters, &matching_block)
222
- @parameters_matcher = ParametersMatcher.new(expected_parameters, &matching_block)
269
+ def with(*expected_parameters_or_matchers, &matching_block)
270
+ @parameters_matcher = ParametersMatcher.new(expected_parameters_or_matchers, self, &matching_block)
223
271
  self
224
272
  end
273
+ ruby2_keywords(:with)
225
274
 
226
275
  # Modifies expectation so that the expected method must be called with a block.
227
276
  #
@@ -644,5 +693,11 @@ module Mocha
644
693
  signature << " #{@block_matcher.mocha_inspect}" if @block_matcher.mocha_inspect
645
694
  signature
646
695
  end
696
+
697
+ # @private
698
+ def definition_location
699
+ filter = BacktraceFilter.new
700
+ filter.filtered(backtrace)[0]
701
+ end
647
702
  end
648
703
  end
data/lib/mocha/inspect.rb CHANGED
@@ -18,9 +18,9 @@ module Mocha
18
18
  end
19
19
 
20
20
  module HashMethods
21
- def mocha_inspect(wrapped = true)
21
+ def mocha_inspect
22
22
  unwrapped = collect { |key, value| "#{key.mocha_inspect} => #{value.mocha_inspect}" }.join(', ')
23
- wrapped ? "{#{unwrapped}}" : unwrapped
23
+ Hash.ruby2_keywords_hash?(self) ? unwrapped : "{#{unwrapped}}"
24
24
  end
25
25
  end
26
26
 
@@ -1,54 +1,26 @@
1
1
  require 'mocha/debug'
2
-
3
2
  require 'mocha/detection/mini_test'
4
-
5
- require 'mocha/integration/mini_test/nothing'
6
- require 'mocha/integration/mini_test/version_13'
7
- require 'mocha/integration/mini_test/version_140'
8
- require 'mocha/integration/mini_test/version_141'
9
- require 'mocha/integration/mini_test/version_142_to_172'
10
- require 'mocha/integration/mini_test/version_200'
11
- require 'mocha/integration/mini_test/version_201_to_222'
12
- require 'mocha/integration/mini_test/version_230_to_2101'
13
- require 'mocha/integration/mini_test/version_2110_to_2111'
14
- require 'mocha/integration/mini_test/version_2112_to_320'
15
3
  require 'mocha/integration/mini_test/adapter'
16
4
 
17
- require 'mocha/deprecation'
18
-
19
5
  module Mocha
20
6
  module Integration
21
7
  module MiniTest
22
8
  def self.activate
23
- return false unless Detection::MiniTest.testcase
24
- mini_test_version = Gem::Version.new(Detection::MiniTest.version)
9
+ target = Detection::MiniTest.testcase
10
+ return false unless target
25
11
 
12
+ mini_test_version = Gem::Version.new(Detection::MiniTest.version)
26
13
  Debug.puts "Detected MiniTest version: #{mini_test_version}"
27
14
 
28
- integration_module = [
29
- MiniTest::Adapter,
30
- MiniTest::Version2112To320,
31
- MiniTest::Version2110To2111,
32
- MiniTest::Version230To2101,
33
- MiniTest::Version201To222,
34
- MiniTest::Version200,
35
- MiniTest::Version142To172,
36
- MiniTest::Version141,
37
- MiniTest::Version140,
38
- MiniTest::Version13,
39
- MiniTest::Nothing
40
- ].detect { |m| m.applicable_to?(mini_test_version) }
15
+ unless MiniTest::Adapter.applicable_to?(mini_test_version)
16
+ raise 'Versions of minitest earlier than v3.3.0 are not supported.'
17
+ end
41
18
 
42
- target = Detection::MiniTest.testcase
43
- unless target < integration_module
44
- unless integration_module == MiniTest::Adapter
45
- Deprecation.warning(
46
- 'Versions of minitest earlier than v3.3.0 will not be supported in future versions of Mocha.'
47
- )
48
- end
49
- Debug.puts "Applying #{integration_module.description}"
50
- target.send(:include, integration_module)
19
+ unless target < MiniTest::Adapter
20
+ Debug.puts "Applying #{MiniTest::Adapter.description}"
21
+ target.send(:include, MiniTest::Adapter)
51
22
  end
23
+
52
24
  true
53
25
  end
54
26
  end