Skip to content

Commit

Permalink
Fail fast if invocation matches never expectation
Browse files Browse the repository at this point in the history
Previously when an invocation matched an expectation which did not allow
invocations (i.e. `Expectation#never` had been called on it), but the
invocation also matched another expectation which did allow invocations,
then the test would not fail with an unexpected invocation error.

This was happening because neither the `if` condition was `true`,
because the "never" expectation was not returned by
`ExpectationList#match_allowing_invocation`, but the other expectation
allowing expectations was returned. Thus `Expectation#invoke` was called
on the latter and `Mock#raise_unexpected_invocation_error` was not
called.

This behaviour was confusing and had led to a number of issues being
raised over the years: #44, #131, #490 & most recently #678. Previously
I had thought this was a symptom of the JMock v1 dispatch behaviour
(which _might_ still be true) and thus would be best addressed by
adopting the JMock v2 dispatch behaviour (#173). However, having
considered this specific scenario involving a "never" expectation, I've
decided to try to fix it with the changes in this commit.

Now a test like this will fail with an unexpected invocation error:

    mock = mock('mock')
    mock.stubs(:method)
    mock.expects(:method).never
    mock.method

    unexpected invocation: #<Mock:mock>.method()
    unsatisfied expectations:
    - expected never, invoked once: #<Mock:mock>.method(any_parameters)
    satisfied expectations:
    - allowed any number of times, invoked never: #<Mock:mock>.method(any_parameters)

Closes #678. Also addresses #490, #131 & #44.
  • Loading branch information
floehopper committed Nov 24, 2024
1 parent 59e0ab1 commit f832c55
Show file tree
Hide file tree
Showing 2 changed files with 12 additions and 18 deletions.
5 changes: 1 addition & 4 deletions lib/mocha/mock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -327,10 +327,7 @@ def handle_method_call(symbol, arguments, block)
matching_expectation_allowing_invocation = matching_expectations.detect(&:invocations_allowed?)
matching_expectation_never_allowing_invocation = matching_expectations.detect(&:invocations_never_allowed?)

if matching_expectation_allowing_invocation
if matching_expectation_never_allowing_invocation
invocation_not_allowed_warning(invocation, matching_expectation_never_allowing_invocation)
end
if matching_expectation_allowing_invocation && !matching_expectation_never_allowing_invocation
matching_expectation_allowing_invocation.invoke(invocation)
else
matching_expectation_ignoring_order = all_expectations.match(invocation, ignoring_order: true)
Expand Down
25 changes: 11 additions & 14 deletions test/acceptance/mocked_methods_dispatch_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,23 +75,20 @@ def test_should_find_latest_expectation_with_range_of_expected_invocation_count_
assert_passed(test_result)
end

def test_should_display_deprecation_warning_if_invocation_matches_expectation_with_never_cardinality
execution_point = nil
def test_should_fail_fast_if_invocation_matches_expectation_with_never_cardinality
test_result = run_as_test do
mock = mock('mock')
mock.stubs(:method)
mock.expects(:method).never; execution_point = ExecutionPoint.current
DeprecationDisabler.disable_deprecations do
mock.method
end
mock.expects(:method).never
mock.method
end
assert_passed(test_result)
message = Mocha::Deprecation.messages.last
location = execution_point.location
expected = [
"The expectation defined at #{location} does not allow invocations, but #<Mock:mock>.method() was invoked.",
'This invocation will cause the test to fail fast in a future version of Mocha.'
]
assert_equal expected.join(' '), message
assert_failed(test_result)
assert_equal [
'unexpected invocation: #<Mock:mock>.method()',
'unsatisfied expectations:',
'- expected never, invoked once: #<Mock:mock>.method(any_parameters)',
'satisfied expectations:',
'- allowed any number of times, invoked never: #<Mock:mock>.method(any_parameters)'
], test_result.failure_message_lines
end
end

0 comments on commit f832c55

Please sign in to comment.