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 are missing coverage. Please review.

Project coverage is 90.08%. Comparing base (3ad902d) to head (448e46a).

Files Patch % Lines
.../Framework/MockObject/Runtime/Stub/ClosureMock.php 71.42% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #5759      +/-   ##
============================================
- Coverage     90.08%   90.08%   -0.01%     
- Complexity     6588     6592       +4     
============================================
  Files           694      695       +1     
  Lines         19952    19961       +9     
============================================
+ Hits          17974    17981       +7     
- Misses         1978     1980       +2     

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

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.
/**
* @mixin \PHPUnit\Framework\MockObject\MockObject
*
* @method InvocationMocker method($constraint)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why Psalm does not recognize this annotation. PhpStorm does, and I used the same annotation as in MockObject:

* @method InvocationMocker method($constraint)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature/test-doubles 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.

None yet

2 participants