Skip to content

Commit 1bcedeb

Browse files
committed
Added ability isolate and verify mock objects within specification
1 parent 52a7541 commit 1bcedeb

File tree

4 files changed

+242
-20
lines changed

4 files changed

+242
-20
lines changed

src/Codeception/Specify.php

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
use Codeception\Specify\Config;
55
use Codeception\Specify\ConfigBuilder;
6+
use Codeception\Specify\ObjectProperty;
67

7-
trait Specify {
8+
trait Specify
9+
{
810

911
private $beforeSpecify = array();
1012
private $afterSpecify = array();
@@ -27,7 +29,7 @@ private function specifyInit()
2729
if (!$this->specifyConfig) $this->specifyConfig = Config::create();
2830
}
2931

30-
function specify($specification, \Closure $callable = null, $params = [])
32+
function specify($specification, \Closure $callable = null, $params = [])
3133
{
3234
if (!$callable) return;
3335
$this->specifyInit();
@@ -38,7 +40,7 @@ function specify($specification, \Closure $callable = null, $params = [])
3840

3941
$this->setName($newName);
4042

41-
$properties = get_object_vars($this);
43+
$properties = $this->getSpecifyObjectProperties();
4244

4345
// prepare for execution
4446
$throws = $this->getSpecifyExpectedException($params);
@@ -58,13 +60,12 @@ function specify($specification, \Closure $callable = null, $params = [])
5860
if ($closure instanceof \Closure) $closure->__invoke();
5961
}
6062
}
63+
6164
$this->specifyExecute($test, $throws, $example);
6265

6366
// restore object properties
64-
foreach ($properties as $property => $val) {
65-
if ($this->specifyConfig->propertyIgnored($property)) continue;
66-
$this->$property = $val;
67-
}
67+
$this->specifyRestoreProperties($properties);
68+
6869
if (!empty($this->afterSpecify) && is_array($this->afterSpecify)) {
6970
foreach ($this->afterSpecify as $closure) {
7071
if ($closure instanceof \Closure) $closure->__invoke();
@@ -120,8 +121,10 @@ private function specifyExecute($test, $throws = false, $examples = array())
120121
}
121122

122123
$result = $this->getTestResultObject();
124+
123125
try {
124126
call_user_func_array($test, $examples);
127+
$this->specifyCheckMockObjects();
125128
} catch (\PHPUnit_Framework_AssertionFailedError $e) {
126129
if ($throws !== get_class($e)){
127130
$result->addFailure(clone($this), $e, $result->time());
@@ -179,29 +182,86 @@ function cleanSpecify()
179182
}
180183

181184
/**
182-
* @param $properties
183-
* @return array
185+
* @param ObjectProperty[] $properties
184186
*/
185187
private function specifyCloneProperties($properties)
186188
{
187-
foreach ($properties as $property => $val) {
188-
if ($this->specifyConfig->propertyIgnored($property)) {
189-
continue;
190-
}
191-
if ($this->specifyConfig->classIgnored($val)) {
189+
foreach ($properties as $property) {
190+
$propertyName = $property->getName();
191+
$propertyValue = $property->getValue();
192+
193+
if ($this->specifyConfig->classIgnored($propertyValue)) {
192194
continue;
193195
}
194196

195-
if ($this->specifyConfig->propertyIsShallowCloned($property)) {
196-
if (is_object($val)) {
197-
$this->$property = clone $val;
197+
if ($this->specifyConfig->propertyIsShallowCloned($propertyName)) {
198+
if (is_object($propertyValue)) {
199+
$property->setValue(clone $propertyValue);
198200
} else {
199-
$this->$property = $val;
201+
$property->setValue($propertyValue);
200202
}
201203
}
202-
if ($this->specifyConfig->propertyIsDeeplyCloned($property)) {
203-
$this->$property = $this->copier->copy($val);
204+
205+
if ($this->specifyConfig->propertyIsDeeplyCloned($propertyName)) {
206+
$property->setValue($this->copier->copy($propertyValue));
204207
}
205208
}
206209
}
210+
211+
/**
212+
* @param ObjectProperty[] $properties
213+
*/
214+
private function specifyRestoreProperties($properties)
215+
{
216+
foreach ($properties as $property) {
217+
$property->restoreValue();
218+
}
219+
}
220+
221+
/**
222+
* @return ObjectProperty[]
223+
*/
224+
private function getSpecifyObjectProperties()
225+
{
226+
$properties = [];
227+
228+
foreach (get_object_vars($this) as $property => $value) {
229+
if ($this->specifyConfig->propertyIgnored($property)) {
230+
continue;
231+
}
232+
233+
$properties[] = new ObjectProperty($this, $property, $value);
234+
}
235+
236+
// isolate mockObjects property from PHPUnit_Framework_TestCase
237+
if (($phpUnitReflection = $this->specifyGetPhpUnitReflection()) !== null) {
238+
$properties[] = $mockObjects = new ObjectProperty(
239+
$this, $phpUnitReflection->getProperty('mockObjects')
240+
);
241+
242+
// remove all mock objects inherited from parent scope(s)
243+
$mockObjects->setValue([]);
244+
}
245+
246+
return $properties;
247+
}
248+
249+
private function specifyCheckMockObjects()
250+
{
251+
if (($phpUnitReflection = $this->specifyGetPhpUnitReflection()) !== null) {
252+
$verifyMockObjects = $phpUnitReflection->getMethod('verifyMockObjects');
253+
$verifyMockObjects->setAccessible(true);
254+
$verifyMockObjects->invoke($this);
255+
}
256+
}
257+
258+
/**
259+
* @return \ReflectionClass|null
260+
*/
261+
private function specifyGetPhpUnitReflection()
262+
{
263+
if ($this instanceof \PHPUnit_Framework_TestCase) {
264+
return new \ReflectionClass('\PHPUnit_Framework_TestCase');
265+
}
266+
}
207267
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
namespace Codeception\Specify;
3+
4+
/**
5+
* Helper for manipulating by an object property.
6+
*
7+
* @author Roman Ishchenko <[email protected]>
8+
*/
9+
class ObjectProperty
10+
{
11+
/**
12+
* @var mixed
13+
*/
14+
private $_owner;
15+
16+
/**
17+
* @var \ReflectionProperty|string
18+
*/
19+
private $_property;
20+
21+
/**
22+
* @var mixed
23+
*/
24+
private $_initValue;
25+
26+
/**
27+
* ObjectProperty constructor.
28+
*
29+
* @param $owner
30+
* @param $property
31+
* @param $value
32+
*/
33+
public function __construct($owner, $property, $value = null)
34+
{
35+
$this->_owner = $owner;
36+
$this->_property = $property;
37+
38+
if (!($this->_property instanceof \ReflectionProperty)) {
39+
$this->_property = new \ReflectionProperty($owner, $this->_property);
40+
}
41+
42+
$this->_property->setAccessible(true);
43+
44+
$this->_initValue = ($value === null ? $this->getValue() : $value);
45+
}
46+
47+
/**
48+
* @return string
49+
*/
50+
public function getName()
51+
{
52+
return $this->_property->getName();
53+
}
54+
55+
/**
56+
* Restores initial value
57+
*/
58+
public function restoreValue()
59+
{
60+
$this->setValue($this->_initValue);
61+
}
62+
63+
/**
64+
* @return mixed
65+
*/
66+
public function getValue()
67+
{
68+
return $this->_property->getValue($this->_owner);
69+
}
70+
71+
/**
72+
* @param mixed $value
73+
*/
74+
public function setValue($value)
75+
{
76+
$this->_property->setValue($this->_owner, $value);
77+
}
78+
}

tests/ObjectPropertyTest.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
require_once __DIR__.'/../vendor/autoload.php';
3+
4+
class ObjectPropertyTest extends \PHPUnit_Framework_TestCase
5+
{
6+
private $private = 'private';
7+
8+
public function testConstruction()
9+
{
10+
$this->prop = 'test';
11+
12+
$prop = new \Codeception\Specify\ObjectProperty($this, 'prop');
13+
14+
$this->assertEquals('prop', $prop->getName());
15+
$this->assertEquals('test', $prop->getValue());
16+
17+
$prop = new \Codeception\Specify\ObjectProperty($this, 'private');
18+
19+
$this->assertEquals('private', $prop->getName());
20+
$this->assertEquals('private', $prop->getValue());
21+
22+
$prop = new \Codeception\Specify\ObjectProperty(
23+
$this, new ReflectionProperty($this, 'private')
24+
);
25+
26+
$this->assertEquals('private', $prop->getName());
27+
$this->assertEquals('private', $prop->getValue());
28+
}
29+
30+
public function testRestore()
31+
{
32+
$this->prop = 'test';
33+
34+
$prop = new \Codeception\Specify\ObjectProperty($this, 'prop');
35+
$prop->setValue('another value');
36+
37+
$this->assertEquals('another value', $this->prop);
38+
39+
$prop->restoreValue();
40+
41+
$this->assertEquals('test', $this->prop);
42+
43+
$prop = new \Codeception\Specify\ObjectProperty($this, 'private');
44+
$prop->setValue('another private value');
45+
46+
$this->assertEquals('another private value', $this->private);
47+
48+
$prop->restoreValue();
49+
50+
$this->assertEquals('private', $this->private);
51+
52+
$prop = new \Codeception\Specify\ObjectProperty($this, 'prop', 'testing');
53+
54+
$this->assertEquals('test', $prop->getValue());
55+
56+
$prop->setValue('Hello, World!');
57+
58+
$this->assertEquals($prop->getValue(), $this->prop);
59+
$this->assertEquals('Hello, World!', $prop->getValue());
60+
61+
$prop->restoreValue();
62+
63+
$this->assertEquals($prop->getValue(), $this->prop);
64+
$this->assertEquals('testing', $prop->getValue());
65+
}
66+
}

tests/SpecifyTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,24 @@ public function testExamplesIndexInName()
279279
});
280280
}
281281

282+
public function testMockObjectsIsolation()
283+
{
284+
$mock = $this->getMock(get_class($this), ['testMockObjectsIsolation']);
285+
$mock->expects($this->once())->method('testMockObjectsIsolation');
286+
287+
$this->specify('this should fail', function () {
288+
$mock = $this->getMock(get_class($this), ['testMockObjectsIsolation']);
289+
$mock->expects($this->exactly(100500))->method('testMockObjectsIsolation');
290+
}, ['throws' => 'PHPUnit_Framework_ExpectationFailedException']);
291+
292+
$this->specify('this should not fail', function () {
293+
$mock = $this->getMock(get_class($this), ['testMockObjectsIsolation']);
294+
$mock->expects($this->never())->method('testMockObjectsIsolation');
295+
});
296+
297+
$mock->testMockObjectsIsolation();
298+
}
299+
282300
// public function testFail()
283301
// {
284302
// $this->specify('this will fail', function(){

0 commit comments

Comments
 (0)