Reputation: 9096
I'm trying to build a dynamic mock (term used loosely) for a few reasons:
In other words: Please don't tell me to go use a mocking library. I already use mocking libraries (I've used at least three PHP libraries extensively), and my decision to try my hand at creating my own solution was a conscious one.
So: I'm trying to do something that conceptually seems rather simple.
How can I dynamically override all methods in my mock class that exist in the class the mock extends?
In other words, if I have class A
which includes method a
, and I have a mock B
which extends class A
, how can I catch all calls to method A
without explicitly implementing method a
in mock class B
?
I've tried to do this with the __call()
magic method, but this won't work because __call()
only catches calls to methods which don't exist.
I'd like to avoid approaches that require large architectural changes. My main requirement here is that any class which requires an instance of class A
in its constructor must not be able to tell that mock B
is not an instance of class A
. Hence my preliminary choice of having mock class B
extend class A
. I'd also rather not have to make large changes to class A
, such as setting its methods to private and having it use __call()
, as well.
Upvotes: 2
Views: 1342
Reputation: 9096
I took @dm03514's advice and jumped into Phokito's source. Phokito, at least, uses a mock builder to generate a mock class definition on the fly which extends the base class and overrides all its methods, and then uses eval
to declare the defined class.
Here's my basic mock builder based on this approach:
class MockFactory
{
public function buildMock( $class )
{
$reflection = new \ReflectionClass( $class );
$mockedShortName = $reflection->getShortName();
$mockShortName = "Mock$mockedShortName";
$mockClass = "\\$mockShortName";
if ( ! class_exists($mockClass) ) {
$this->declareMockClass( $reflection, $mockShortName, $mockedShortName );
}
return new $mockClass;
}
private function declareMockClass( $reflection, $mockShortName, $mockedShortName )
{
$php = [];
$mockedNamespace = $reflection->getNamespaceName();
$extends = $reflection->isInterface() ? 'implements' : 'extends';
$php[] = <<<EOT
class $mockShortName $extends $mockedNamespace\\$mockedShortName {
public function setReturnValue( \$method, \$returnValue ) {
\$this->\$method = \$returnValue;
}
public function getCalls ( \$method ) {
\$callsProperty = \$method . "Calls";
return \$this->\$callsProperty;
}
EOT;
foreach ( $reflection->getMethods() as $method ) {
$methodName = $method->name;
$params = [];
foreach ( $method->getParameters() as $i => $parameter ) {
if ( $parameter->isArray() ) $type = 'array ';
else if ( $parameterClass = $parameter->getClass() ) $type = '\\' . $parameterClass->getName() . ' ';
else $type = '';
$params[] = "$type \${$parameter->getName()}";
}
$paramString = implode( ',', $params );
$php[] = <<<EOT
private \${$methodName}Calls = [];
public function $methodName($paramString) {
\$this->{$methodName}Calls[] = func_get_args();
return \$this->$methodName;
}
EOT;
}
$php[] = '}';
$toEval = implode( "\n\n", $php );
eval( $toEval );
}
}
Upvotes: 0
Reputation: 394
Something like that:
class A
{
public function foo()
{
return __CLASS__;
}
}
class B extends A
{
public function foo()
{
return __CLASS__;
}
}
$b = new B();
$reflection = new ReflectionObject($b);
$parentReflection = $reflection->getParentClass();
$parentFooReflection = $parentReflection->getMethod('foo');
$data = $parentFooReflection->invoke($b);
echo $data;
Notice that you can pass methods arguments after first argument in the invoke(object $object, methods argument1, methods argument2...)
function
You can do a lot of stuff like that with Reflections, for more info check out the link http://php.net/manual/en/book.reflection.php
Upvotes: 0