-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Deprecate MockBuilder::addMethods()
#5320
Comments
In one of my projects, I wanted to use PHPUnit mocks to check that an anonymous function was being called X times. I mocked To fix this deprecation message, I've introduced a new class into my tests directory called class ExampleTest extends MyTestCase {
public function testAnonFuncIsCalled10Times():void {
$sut = new MyObject();
// this will pass in a callable to `doSomething10Times` that must be called 10 times with the provided parameters
$sut->doSomething10Times(self::mockCallable(10, "example param"));
}
}
class MyTestCase extends TestCase {
/** @return MockObject|callable */
protected function mockCallable(int $numCalls = null, ...$expectedParams):MockObject {
$mock = self::createMock(MockedCaller::class);
if(!is_null($numCalls)) {
$expectation = $mock->expects(self::exactly($numCalls))
->method("__invoke");
if(!empty($expectedParameters)) {
foreach($expectedParameters as $p) {
$expectation->with(self::identicalTo($p));
}
}
}
return $mock;
}
}
class MockedCaller {
public function __invoke():void {}
} Hope this helps someone! |
I was wondering, what is the correct way of handling this deprecation/removal? I'm referring to cases where you're mocking an interface which contains a method listed as a phpdoc method for backwards compatibility reasons, and does not exist within the interface itself. I understand it's somewhat of a weird scenario, but still technically valid. |
@pkly We use either an anonymous class like this
or straight up another class (inside the TestCase class or in a separate file) |
Declare a new interface that extends that interface and materialize the virtual method. /**
* @method void foo(string $a)
*/
interface MyInterface {}
interface MyMockableInterface extends MyInterface
{
public function foo(string $a): void;
}
$this->createMock(MyMockableInterface::class); |
Is there some other way to mock e.g. calls to Or some other suggestions for this case? |
How will it be possible to mock methods from a trait in the future if I can no longer specify them with addMethods()? |
A slightly more flexible solution to @g105b 's MockedCaller class is defining it as a simple generic interface: /**
* @see https://github.com/sebastianbergmann/phpunit/issues/5320
*
* @template T
*/
interface CallableMock
{
/**
* @return T
*/
public function __invoke();
} This does not force a void return type, and makes tools like PHPStan happier because you can type hint the callable return type: /** @var MockObject&CallableMock<\stdClass> $callableMock */
$callableMock = $this->createMock(CallableMock::class);
$callableMock->expects($this->once())
->method('__invoke')
->willReturnCallback(function (): \stdClass {
return new \stdClass();
});
// Assuming method() has @param callable(): \stdClass $argument
$result = $someSubject->method($callableMock->__invoke(...)); |
@NanoSector answer can be extended with a interface CallableMock
{
/**
* @return T
*/
public function __invoke();
/**
* @return T
*/
public function __call(string $name, array $arguments);
} The best way I have found to handle them is with a $callableMock = $this->createMock(CallableMock::class);
$callableMock->expects($this->any())
->method('__call')
->willReturnCallback(fn(string $name): string => match ($name) {
'a' => 'foo',
'b' => 'bar',
});
echo $callableMock->a(); // foo
echo $callableMock->b(); // bar
echo $callableMock->c(); // UnhandledMatchError |
To reduce complexity inside PHPUnit's test double functionality,
MockBuilder::addMethods()
will be deprecated and then removed:@deprecated
annotation to the method declaration)The text was updated successfully, but these errors were encountered: