Skip to content

Commit

Permalink
Merge pull request #12 from tomaszdurka/issue-12
Browse files Browse the repository at this point in the history
Add interfaces support
  • Loading branch information
tomaszdurka committed Jun 23, 2014
2 parents e22a60f + 18325f6 commit 60ce1c0
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 53 deletions.
86 changes: 45 additions & 41 deletions source/Mocka/ClassMock.php
Expand Up @@ -10,30 +10,28 @@ class ClassMock {
/** @var string */
private $_className;

/** @var string */
private $_name;

/** @var string */
private $_namespace;

/** @var string */
/** @var string|null */
private $_parentClassName;

/** @var array */
private $_interfaces;

/** @var MethodMock[] */
private $_mockedMethods = array();

/** @var MethodMock[] */
private $_mockedStaticMethods = array();

/**
* @param string $className
* @param string $className
* @param array|null $interfaces
*/
public function __construct($className) {
$this->_className = $className . uniqid();
$parts = array_filter(explode('\\', $this->_className));
$this->_name = array_pop($parts);
$this->_namespace = join('\\', $parts);
$this->_parentClassName = (string) $className;
public function __construct($className, array $interfaces = null) {
$this->_className = 'Mocka' . uniqid();
if (null !== $className) {
$this->_parentClassName = (string) $className;
}
$this->_interfaces = (array) $interfaces;

$this->_load();
}
Expand All @@ -45,20 +43,6 @@ public function getClassName() {
return $this->_className;
}

/**
* @return string
*/
public function getName() {
return $this->_name;
}

/**
* @return string
*/
public function getNamespace() {
return $this->_namespace;
}

/**
* @param array|null $constructorArgs
* @return \Mocka\ClassTrait
Expand All @@ -73,20 +57,16 @@ public function newInstance(array $constructorArgs = null) {
* @return string
*/
public function generateCode() {

$class = new ClassBlock($this->getName(), $this->_parentClassName);
$class->setNamespace($this->getNamespace());
$class = new ClassBlock($this->getClassName());
if ($this->_parentClassName) {
$class->setParentClassName($this->_parentClassName);
}
foreach ($this->_interfaces as $interface) {
$class->addInterface($interface);
}
$class->addUse('\Mocka\ClassTrait');

$reflectionTrait = new \ReflectionClass('\\Mocka\\ClassTrait');
$traitMethods = array_map(function (\ReflectionMethod $method) {
return $method->getName();
}, $reflectionTrait->getMethods());
$reflectionClass = new \ReflectionClass($this->_parentClassName);
foreach ($reflectionClass->getMethods() as $reflectionMethod) {
if ($reflectionMethod->isPrivate() || $reflectionMethod->isFinal() || in_array($reflectionMethod->getName(), $traitMethods)) {
continue;
}
foreach ($this->_getMockableMethods() as $reflectionMethod) {
$method = new MethodBlock($reflectionMethod->getName());
$method->setAbstract(false);
$method->setParametersFromReflection($reflectionMethod);
Expand All @@ -102,7 +82,7 @@ public function generateCode() {
});
}
$class->addMethod($method);
};
}
return $class->dump();
}

Expand Down Expand Up @@ -181,4 +161,28 @@ private function _load() {
$className = $this->getClassName();
$className::setMockClass($this);
}

/**
* @return \ReflectionMethod[]
*/
private function _getMockableMethods() {
/** @var \ReflectionMethod[] $methods */
$methods = array();
$interfaces = $this->_interfaces;
if ($this->_parentClassName) {
$interfaces[] = $this->_parentClassName;
}
foreach ($interfaces as $interface) {
$reflectionClass = new \ReflectionClass($interface);
foreach ($reflectionClass->getMethods() as $method) {
$methods[$method->getName()] = $method;
}
}

$reflectionTrait = new \ReflectionClass('\\Mocka\\ClassTrait');
$methods = array_filter($methods, function (\ReflectionMethod $reflectionMethod) use ($reflectionTrait) {
return !$reflectionMethod->isPrivate() && !$reflectionMethod->isFinal() && !$reflectionTrait->hasMethod($reflectionMethod->getName());
});
return $methods;
}
}
16 changes: 13 additions & 3 deletions source/Mocka/MockaTrait.php
Expand Up @@ -5,11 +5,21 @@
trait MockaTrait {

/**
* @param string $className
* @param string $className
* @param array|null $interfaces
* @return \Mocka\ClassMock
*/
public function mockClass($className) {
return new ClassMock($className);
public function mockClass($className, array $interfaces = null) {
return new ClassMock($className, $interfaces);
}

/**
* @param string $interfaceName
* @return ClassMock
*/
public function mockInterface($interfaceName) {
$interfaceName = (string) $interfaceName;
return new ClassMock(null, [$interfaceName]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/mocks/AbstractClass.php
Expand Up @@ -2,7 +2,7 @@

namespace MockaMocks;

abstract class AbstractClass {
abstract class AbstractClass implements InterfaceMock{

public $constructorArgs;

Expand Down
10 changes: 10 additions & 0 deletions tests/mocks/InterfaceMock.php
@@ -0,0 +1,10 @@
<?php

namespace MockaMocks;

interface InterfaceMock {

public function zoo();

public function interfaceMethod();
}
36 changes: 29 additions & 7 deletions tests/source/Mocka/ClassMockTest.php
Expand Up @@ -12,12 +12,9 @@ public function testGenerateCode() {
$parentClassName = '\\MockaMocks\\AbstractClass';

$classMock = new ClassMock($parentClassName);
$name = $classMock->getName();
$namespace = $classMock->getNamespace();
$className = $classMock->getClassName();
$expectedMockCode = <<<EOD
namespace $namespace;
class $name extends $parentClassName {
class $className extends $parentClassName {
use \\Mocka\\ClassTrait;
Expand All @@ -44,6 +41,10 @@ public static function jar() {
protected static function _jar() {
return static::_callStaticMethod(__FUNCTION__, func_get_args());
}
public function interfaceMethod() {
return \$this->_callMethod(__FUNCTION__, func_get_args());
}
}
EOD;
$this->assertSame($expectedMockCode, $classMock->generateCode());
Expand Down Expand Up @@ -77,12 +78,12 @@ public function testMockStaticMethod() {
$className = $classMock->getClassName();

$this->assertSame('jar', $className::jar());
$classMock->mockStaticMethod('jar')->set(function() {
$classMock->mockStaticMethod('jar')->set(function () {
return 'foo';
});
$this->assertSame('foo', $className::jar());

$classMock->mockStaticMethod('nonexistent')->set(function() {
$classMock->mockStaticMethod('nonexistent')->set(function () {
return 'bar';
});
$this->assertSame('bar', $className::nonexistent());
Expand All @@ -95,4 +96,25 @@ public function testNewInstanceConstructorArgs() {
$object = $classMock->newInstance($constructorArgs);
$this->assertSame($object->constructorArgs, $constructorArgs);
}

public function testGenerateCodeInterface() {
$parentInterfaceName = '\\MockaMocks\\InterfaceMock';
$classMock = new ClassMock(null, [$parentInterfaceName]);
$className = $classMock->getClassName();
$expectedMockCode = <<<EOD
class $className implements $parentInterfaceName {
use \\Mocka\\ClassTrait;
public function zoo() {
return \$this->_callMethod(__FUNCTION__, func_get_args());
}
public function interfaceMethod() {
return \$this->_callMethod(__FUNCTION__, func_get_args());
}
}
EOD;
$this->assertSame($expectedMockCode, $classMock->generateCode());
}
}
12 changes: 12 additions & 0 deletions tests/source/Mocka/ClassTraitTest.php
Expand Up @@ -6,6 +6,18 @@

class ClassTraitTest extends \PHPUnit_Framework_TestCase {

public function testMockClass() {
$mocka = new Mocka();
$mockClass = $mocka->mockClass('\MockaMocks\AbstractClass');
$this->assertInstanceOf('\\Mocka\\ClassMock', $mockClass);
}

public function testMockInterface() {
$mocka = new Mocka();
$mockClass = $mocka->mockInterface('\MockaMocks\InterfaceMock');
$this->assertInstanceOf('\\Mocka\\ClassMock', $mockClass);
}

public function testCallMockedMethod() {
$mocka = new Mocka();
$mockClass = $mocka->mockClass('\MockaMocks\AbstractClass');
Expand Down
1 change: 0 additions & 1 deletion tests/source/Mocka/MockaTest.php
Expand Up @@ -12,7 +12,6 @@ public function testMockClass() {
$className = $mock->getClassName();
$this->assertTrue(is_subclass_of($className, '\\Mocka\\Mocka'));
$this->assertNotSame('\\Mocka\\Mocka', $className);
$this->assertStringStartsWith('\\Mocka\\Mocka', $className);

$classNameOther = $mocka->mockClass('\\Mocka\\Mocka');
$this->assertNotSame($className, $classNameOther);
Expand Down

0 comments on commit 60ce1c0

Please sign in to comment.