From 191f1f5aec08c543fecb2ddbe2578873fc4c9b49 Mon Sep 17 00:00:00 2001 From: James Mead Date: Sun, 8 Dec 2024 18:04:19 +0000 Subject: [PATCH 1/8] Clarify docs for Expectation#with params The `expected_parameters_or_matchers` argument in the actual method signature is already splatted and so it makes more sense that the `param` YARD tag type does not use a splat. --- lib/mocha/expectation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index f55918bd..41700949 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -202,7 +202,7 @@ def at_most_once # @see ParameterMatchers # @see Configuration#strict_keyword_argument_matching= # - # @param [*Array] expected_parameters_or_matchers expected parameter values or parameter matchers. + # @param [Array] expected_parameters_or_matchers expected parameter values or parameter matchers. # @yield optional block specifying custom matching. # @yieldparam [*Array] actual_parameters parameters with which expected method was invoked. # @yieldreturn [Boolean] +true+ if +actual_parameters+ are acceptable. From c3b4b8ea0c7632154d028b94e74cac03c5135e9c Mon Sep 17 00:00:00 2001 From: James Mead Date: Sun, 8 Dec 2024 18:04:19 +0000 Subject: [PATCH 2/8] Clarify docs for Expectation#with yieldparam This seems more consistent with what we've done with the `param` YARD tag type. --- lib/mocha/expectation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 41700949..cf794c10 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -204,7 +204,7 @@ def at_most_once # # @param [Array] expected_parameters_or_matchers expected parameter values or parameter matchers. # @yield optional block specifying custom matching. - # @yieldparam [*Array] actual_parameters parameters with which expected method was invoked. + # @yieldparam [Array] actual_parameters parameters with which expected method was invoked. # @yieldreturn [Boolean] +true+ if +actual_parameters+ are acceptable. # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained. # From 99f3f6bf16956822b632e83fdda2e6c8ecdc5b80 Mon Sep 17 00:00:00 2001 From: James Mead Date: Sun, 8 Dec 2024 18:11:35 +0000 Subject: [PATCH 3/8] Document Expectation#with called multiple times This is accidental and possibly undesirable behaviour, but it seems like an improvement to at least document the current behaviour. --- lib/mocha/expectation.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index cf794c10..b3b6c095 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -193,6 +193,8 @@ def at_most_once # # 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. # + # Note that if {#with} is called multiple times on the same expectation, the last call takes precedence; other calls are ignored. + # # 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. # # 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. From 558d52161676298ed41f51ca830190ae63f120da Mon Sep 17 00:00:00 2001 From: James Mead Date: Sun, 8 Dec 2024 18:17:05 +0000 Subject: [PATCH 4/8] Add docs for Expectation#with when passed block Although there was already an example for this and the `yield`, `yieldparam` & `yieldreturn` YARD tags were specified, I think it's helpful to add some explanatory text to the method description. Closes #682. --- lib/mocha/expectation.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index b3b6c095..ebae85e7 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -193,6 +193,8 @@ def at_most_once # # 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. # + # Alternatively a block argument can be passed to {#with} to implement custom parameter matching. The block receives the +*actual_parameters+ as its arguments and should return +true+ if they are acceptable or +false+ otherwise. See the example below where a method is expected to be called with a value divisible by 4. + # # Note that if {#with} is called multiple times on the same expectation, the last call takes precedence; other calls are ignored. # # 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. @@ -207,7 +209,7 @@ def at_most_once # @param [Array] expected_parameters_or_matchers expected parameter values or parameter matchers. # @yield optional block specifying custom matching. # @yieldparam [Array] actual_parameters parameters with which expected method was invoked. - # @yieldreturn [Boolean] +true+ if +actual_parameters+ are acceptable. + # @yieldreturn [Boolean] +true+ if +actual_parameters+ are acceptable; +false+ otherwise. # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained. # # @example Expected method must be called with exact parameter values. @@ -258,7 +260,7 @@ def at_most_once # example.foo('a', { bar: 'b' }) # # This now fails as expected # - # @example Expected method must be called with a value divisible by 4. + # @example Using a block argument to expect the method to be called with a value divisible by 4. # object = mock() # object.expects(:expected_method).with() { |value| value % 4 == 0 } # object.expected_method(16) From 3ca865f4351e5f04d17e885cc8a8a6f977027540 Mon Sep 17 00:00:00 2001 From: James Mead Date: Sun, 8 Dec 2024 18:22:11 +0000 Subject: [PATCH 5/8] Document argument precedence for Expectation#with This is accidental and possibly undesirable behaviour, but it seems like an improvement to at least document the current behaviour. Clarifies potentially confusing behaviour which resulted in the opening of #606. --- lib/mocha/expectation.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index ebae85e7..8c27f059 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -194,6 +194,7 @@ def at_most_once # 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. # # Alternatively a block argument can be passed to {#with} to implement custom parameter matching. The block receives the +*actual_parameters+ as its arguments and should return +true+ if they are acceptable or +false+ otherwise. See the example below where a method is expected to be called with a value divisible by 4. + # The block argument takes precedence over +expected_parameters_or_matchers+. # # Note that if {#with} is called multiple times on the same expectation, the last call takes precedence; other calls are ignored. # From 17a4856454740579439737fa621379b9e2349850 Mon Sep 17 00:00:00 2001 From: James Mead Date: Sun, 8 Dec 2024 18:26:04 +0000 Subject: [PATCH 6/8] Improve docs for Expectation#with when passed block Addressed an aside in the description of #682. This originally came up in this commit [1]. [1]: https://github.com/freerange/mocha/commit/73c4ea6ef5def4f9203e7a0d6f3a460849d511bb --- lib/mocha/expectation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 8c27f059..87a6eeb7 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -194,7 +194,7 @@ def at_most_once # 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. # # Alternatively a block argument can be passed to {#with} to implement custom parameter matching. The block receives the +*actual_parameters+ as its arguments and should return +true+ if they are acceptable or +false+ otherwise. See the example below where a method is expected to be called with a value divisible by 4. - # The block argument takes precedence over +expected_parameters_or_matchers+. + # The block argument takes precedence over +expected_parameters_or_matchers+. The block may be called multiple times per invocation of the expected method and so it should be idempotent. # # Note that if {#with} is called multiple times on the same expectation, the last call takes precedence; other calls are ignored. # From fed33879c42a7f6cda667495bfca62b04b47a9d4 Mon Sep 17 00:00:00 2001 From: James Mead Date: Sun, 8 Dec 2024 18:30:14 +0000 Subject: [PATCH 7/8] Add custom matcher example to Expectatation#with docs --- lib/mocha/expectation.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 87a6eeb7..f7c06b51 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -271,6 +271,22 @@ def at_most_once # object.expects(:expected_method).with() { |value| value % 4 == 0 } # object.expected_method(17) # # => verify fails + # + # @example Extracting a custom matcher into an instance method on the test class. + # class MyTest < Minitest::Test + # def test_expected_method_is_called_with_a_value_divisible_by_4 + # object = mock() + # object.expects(:expected_method).with(&method(:divisible_by_4)) + # object.expected_method(16) + # # => verify succeeds + # end + # + # private + # + # def divisible_by_4(value) + # value % 4 == 0 + # end + # end def with(*expected_parameters_or_matchers, &matching_block) @parameters_matcher = ParametersMatcher.new(expected_parameters_or_matchers, self, &matching_block) self From b30e44344e9faa5b5e2b9f0bbb817e3fbf276eaf Mon Sep 17 00:00:00 2001 From: James Mead Date: Sun, 8 Dec 2024 18:31:44 +0000 Subject: [PATCH 8/8] Indicate when matcher logic is defined by block Previously the output from a test where such an exception was not matched was a bit confusing, because no expected parameters were displayed. While we can't easily display the logic from the block itself, we can at least highlight that such logic does exist. While this will change the content of some test failure messages, I have previously specified that is not part of the documented API. [1] [1]: https://github.com/freerange/mocha/issues/577 --- lib/mocha/parameters_matcher.rb | 10 +++++++--- test/unit/parameters_matcher_test.rb | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/mocha/parameters_matcher.rb b/lib/mocha/parameters_matcher.rb index 72645fe6..da1abb54 100644 --- a/lib/mocha/parameters_matcher.rb +++ b/lib/mocha/parameters_matcher.rb @@ -22,9 +22,13 @@ def parameters_match?(actual_parameters) end def mocha_inspect - signature = matchers.mocha_inspect - signature = signature.gsub(/^\[|\]$/, '') - "(#{signature})" + if @matching_block + '(arguments_accepted_by_custom_matching_block)' + else + signature = matchers.mocha_inspect + signature = signature.gsub(/^\[|\]$/, '') + "(#{signature})" + end end def matchers diff --git a/test/unit/parameters_matcher_test.rb b/test/unit/parameters_matcher_test.rb index 53ffafa7..a97d2d4a 100644 --- a/test/unit/parameters_matcher_test.rb +++ b/test/unit/parameters_matcher_test.rb @@ -103,4 +103,9 @@ def test_should_indicate_that_matcher_will_match_any_actual_parameters parameters_matcher = ParametersMatcher.new assert_equal '(any_parameters)', parameters_matcher.mocha_inspect end + + def test_should_indicate_that_matcher_logic_is_defined_by_custom_block + parameters_matcher = ParametersMatcher.new { true } + assert_equal '(arguments_accepted_by_custom_matching_block)', parameters_matcher.mocha_inspect + end end