Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 80 additions & 20 deletions src/Codeception/Specify.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

use Codeception\Specify\Config;
use Codeception\Specify\ConfigBuilder;
use Codeception\Specify\ObjectProperty;

trait Specify {
trait Specify
{

private $beforeSpecify = array();
private $afterSpecify = array();
Expand All @@ -27,7 +29,7 @@ private function specifyInit()
if (!$this->specifyConfig) $this->specifyConfig = Config::create();
}

function specify($specification, \Closure $callable = null, $params = [])
function specify($specification, \Closure $callable = null, $params = [])
{
if (!$callable) return;
$this->specifyInit();
Expand All @@ -38,7 +40,7 @@ function specify($specification, \Closure $callable = null, $params = [])

$this->setName($newName);

$properties = get_object_vars($this);
$properties = $this->getSpecifyObjectProperties();

// prepare for execution
$throws = $this->getSpecifyExpectedException($params);
Expand All @@ -58,13 +60,12 @@ function specify($specification, \Closure $callable = null, $params = [])
if ($closure instanceof \Closure) $closure->__invoke();
}
}

$this->specifyExecute($test, $throws, $example);

// restore object properties
foreach ($properties as $property => $val) {
if ($this->specifyConfig->propertyIgnored($property)) continue;
$this->$property = $val;
}
$this->specifyRestoreProperties($properties);

if (!empty($this->afterSpecify) && is_array($this->afterSpecify)) {
foreach ($this->afterSpecify as $closure) {
if ($closure instanceof \Closure) $closure->__invoke();
Expand Down Expand Up @@ -120,8 +121,10 @@ private function specifyExecute($test, $throws = false, $examples = array())
}

$result = $this->getTestResultObject();

try {
call_user_func_array($test, $examples);
$this->specifyCheckMockObjects();
} catch (\PHPUnit_Framework_AssertionFailedError $e) {
if ($throws !== get_class($e)){
$result->addFailure(clone($this), $e, $result->time());
Expand Down Expand Up @@ -179,29 +182,86 @@ function cleanSpecify()
}

/**
* @param $properties
* @return array
* @param ObjectProperty[] $properties
*/
private function specifyCloneProperties($properties)
{
foreach ($properties as $property => $val) {
if ($this->specifyConfig->propertyIgnored($property)) {
continue;
}
if ($this->specifyConfig->classIgnored($val)) {
foreach ($properties as $property) {
$propertyName = $property->getName();
$propertyValue = $property->getValue();

if ($this->specifyConfig->classIgnored($propertyValue)) {
continue;
}

if ($this->specifyConfig->propertyIsShallowCloned($property)) {
if (is_object($val)) {
$this->$property = clone $val;
if ($this->specifyConfig->propertyIsShallowCloned($propertyName)) {
if (is_object($propertyValue)) {
$property->setValue(clone $propertyValue);
} else {
$this->$property = $val;
$property->setValue($propertyValue);
}
}
if ($this->specifyConfig->propertyIsDeeplyCloned($property)) {
$this->$property = $this->copier->copy($val);

if ($this->specifyConfig->propertyIsDeeplyCloned($propertyName)) {
$property->setValue($this->copier->copy($propertyValue));
}
}
}

/**
* @param ObjectProperty[] $properties
*/
private function specifyRestoreProperties($properties)
{
foreach ($properties as $property) {
$property->restoreValue();
}
}

/**
* @return ObjectProperty[]
*/
private function getSpecifyObjectProperties()
{
$properties = [];

foreach (get_object_vars($this) as $property => $value) {
if ($this->specifyConfig->propertyIgnored($property)) {
continue;
}

$properties[] = new ObjectProperty($this, $property, $value);
}

// isolate mockObjects property from PHPUnit_Framework_TestCase
if (($phpUnitReflection = $this->specifyGetPhpUnitReflection()) !== null) {
$properties[] = $mockObjects = new ObjectProperty(
$this, $phpUnitReflection->getProperty('mockObjects')
);

// remove all mock objects inherited from parent scope(s)
$mockObjects->setValue([]);
}

return $properties;
}

private function specifyCheckMockObjects()
{
if (($phpUnitReflection = $this->specifyGetPhpUnitReflection()) !== null) {
$verifyMockObjects = $phpUnitReflection->getMethod('verifyMockObjects');
$verifyMockObjects->setAccessible(true);
$verifyMockObjects->invoke($this);
}
}

/**
* @return \ReflectionClass|null
*/
private function specifyGetPhpUnitReflection()
{
if ($this instanceof \PHPUnit_Framework_TestCase) {
return new \ReflectionClass('\PHPUnit_Framework_TestCase');
}
}
}
78 changes: 78 additions & 0 deletions src/Codeception/Specify/ObjectProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php
namespace Codeception\Specify;

/**
* Helper for manipulating by an object property.
*
* @author Roman Ishchenko <roman@ishchenko.ck.ua>
*/
class ObjectProperty
{
/**
* @var mixed
*/
private $_owner;

/**
* @var \ReflectionProperty|string
*/
private $_property;

/**
* @var mixed
*/
private $_initValue;

/**
* ObjectProperty constructor.
*
* @param $owner
* @param $property
* @param $value
*/
public function __construct($owner, $property, $value = null)
{
$this->_owner = $owner;
$this->_property = $property;

if (!($this->_property instanceof \ReflectionProperty)) {
$this->_property = new \ReflectionProperty($owner, $this->_property);
}

$this->_property->setAccessible(true);

$this->_initValue = ($value === null ? $this->getValue() : $value);
}

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

/**
* Restores initial value
*/
public function restoreValue()
{
$this->setValue($this->_initValue);
}

/**
* @return mixed
*/
public function getValue()
{
return $this->_property->getValue($this->_owner);
}

/**
* @param mixed $value
*/
public function setValue($value)
{
$this->_property->setValue($this->_owner, $value);
}
}
66 changes: 66 additions & 0 deletions tests/ObjectPropertyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
require_once __DIR__.'/../vendor/autoload.php';

class ObjectPropertyTest extends \PHPUnit_Framework_TestCase
{
private $private = 'private';

public function testConstruction()
{
$this->prop = 'test';

$prop = new \Codeception\Specify\ObjectProperty($this, 'prop');

$this->assertEquals('prop', $prop->getName());
$this->assertEquals('test', $prop->getValue());

$prop = new \Codeception\Specify\ObjectProperty($this, 'private');

$this->assertEquals('private', $prop->getName());
$this->assertEquals('private', $prop->getValue());

$prop = new \Codeception\Specify\ObjectProperty(
$this, new ReflectionProperty($this, 'private')
);

$this->assertEquals('private', $prop->getName());
$this->assertEquals('private', $prop->getValue());
}

public function testRestore()
{
$this->prop = 'test';

$prop = new \Codeception\Specify\ObjectProperty($this, 'prop');
$prop->setValue('another value');

$this->assertEquals('another value', $this->prop);

$prop->restoreValue();

$this->assertEquals('test', $this->prop);

$prop = new \Codeception\Specify\ObjectProperty($this, 'private');
$prop->setValue('another private value');

$this->assertEquals('another private value', $this->private);

$prop->restoreValue();

$this->assertEquals('private', $this->private);

$prop = new \Codeception\Specify\ObjectProperty($this, 'prop', 'testing');

$this->assertEquals('test', $prop->getValue());

$prop->setValue('Hello, World!');

$this->assertEquals($prop->getValue(), $this->prop);
$this->assertEquals('Hello, World!', $prop->getValue());

$prop->restoreValue();

$this->assertEquals($prop->getValue(), $this->prop);
$this->assertEquals('testing', $prop->getValue());
}
}
18 changes: 18 additions & 0 deletions tests/SpecifyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,24 @@ public function testExamplesIndexInName()
});
}

public function testMockObjectsIsolation()
{
$mock = $this->getMock(get_class($this), ['testMockObjectsIsolation']);
$mock->expects($this->once())->method('testMockObjectsIsolation');

$this->specify('this should fail', function () {
$mock = $this->getMock(get_class($this), ['testMockObjectsIsolation']);
$mock->expects($this->exactly(100500))->method('testMockObjectsIsolation');
}, ['throws' => 'PHPUnit_Framework_ExpectationFailedException']);

$this->specify('this should not fail', function () {
$mock = $this->getMock(get_class($this), ['testMockObjectsIsolation']);
$mock->expects($this->never())->method('testMockObjectsIsolation');
});

$mock->testMockObjectsIsolation();
}

// public function testFail()
// {
// $this->specify('this will fail', function(){
Expand Down