Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add closure mocks #5759

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Conversation

mnapoli
Copy link
Contributor

@mnapoli mnapoli commented Mar 20, 2024

The problem

Mocking closures is doable today by mocking __invoke on an invokable class. However, this is troublesome, as it requires writing an invokable class and mocking a class method (which is confusing, hard to figure out, and makes tests harder to understand afterward).

Here is another approach that is verbose and fragile:

    public function test_validation(): void
    {
        $rule = new CustomLaravelValidationRule();
        
        $failed = false;
        $rule->validate('code', 'abcd', function () use (&$failed) {
            return $failed = true;
        });
        $this->assertTrue($failed);
    }

The solution

This new helper makes things easier via a new $this->createClosureMock() method.

$mock = $this->createClosureMock();

$mock->expectsClosure($this->once())
    ->willReturn(123);

$this->assertSame(123, $mock());

Here's the test above rewritten using the new helper:

    public function test_validation(): void
    {
        $rule = new CustomLaravelValidationRule();
        
        $mock = $this->createClosureMock();
        $mock->expectsClosure($this->never());

        $rule->validate('code', 'abcd', $mock);
    }

This is essentially just a shortcut to the workaround that is doable today, so this PR should not introduce too much new code.

Related to #3536

Note that #3536 mentioned a workaround using stdClass. I was not able to make it work: PHPUnit refused to mock stdClass's __invoke method because it does not exist.

This is, to me, yet another reason to have such a helper.

@mnapoli mnapoli force-pushed the closure-mocks branch 2 times, most recently from 512dd4b to acdef4d Compare March 20, 2024 11:17
Copy link

codecov bot commented Mar 20, 2024

Codecov Report

Attention: Patch coverage is 77.77778% with 2 lines in your changes missing coverage. Please review.

Project coverage is 94.43%. Comparing base (a6390f9) to head (f94fa26).

Files Patch % Lines
.../Framework/MockObject/Runtime/Stub/ClosureMock.php 71.42% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #5759      +/-   ##
============================================
- Coverage     94.43%   94.43%   -0.01%     
- Complexity     6663     6667       +4     
============================================
  Files           709      710       +1     
  Lines         20104    20113       +9     
============================================
+ Hits          18986    18993       +7     
- Misses         1118     1120       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@sebastianbergmann sebastianbergmann added type/enhancement A new idea that should be implemented feature/test-doubles Test Stubs and Mock Objects labels Mar 20, 2024
@mnapoli mnapoli force-pushed the closure-mocks branch 2 times, most recently from 9b33b1b to 448e46a Compare March 20, 2024 11:24
Mocking closures is doable today by mocking __invoke on an invokable class. However this is troublesome, as it requires writing an invokable class, and mocking a class method.

This new helper makes things easier via a new `$this->createClosureMock()` method.
@mnapoli
Copy link
Contributor Author

mnapoli commented Aug 15, 2024

I rebased and the static analysis error was solved 🎉

This is ready to be reviewed/merged.

The code coverage is not happy that __invoke() is not covered, but this is an empty method and it's the one being mocked, so that makes sense, I think it's perfectly fine that it's not covered. If you want me to find a way to force coverage let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature/test-doubles Test Stubs and Mock Objects type/enhancement A new idea that should be implemented
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants