Skip to content

Commit bed0532

Browse files
committed
Merge branch 'master' of github.com:Codeception/Specify
2 parents 0b4b09a + 21b586f commit bed0532

File tree

11 files changed

+377
-43
lines changed

11 files changed

+377
-43
lines changed

.travis.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ language: php
33
php:
44
- 5.4
55
- 5.5
6+
- 5.6
7+
- 7
68

79
before_script:
8-
- composer install
10+
- composer install

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ Specify
33

44
BDD style code blocks for PHPUnit / Codeception
55

6-
Specify allows you to write your tests in more readable BDD style, the same way you might have experienced with [Jasmine](http://pivotal.github.io/jasmine/).
6+
Specify allows you to write your tests in more readable BDD style, the same way you might have experienced with [Jasmine](https://jasmine.github.io/).
77
Inspired by MiniTest of Ruby now you combine BDD and classical TDD style in one test.
88

99
[![Build Status](https://travis-ci.org/Codeception/Specify.png?branch=master)](https://travis-ci.org/Codeception/Specify) [![Latest Stable Version](https://poser.pugx.org/codeception/specify/v/stable.png)](https://packagist.org/packages/codeception/specify)
1010

1111
Additionaly, we recommend to combine this with [**Codeception/Verify**](https://github.com/Codeception/Verify) library, to get BDD style assertions.
1212

1313
``` php
14-
<?
14+
<?php
1515
class UserTest extends PHPUnit_Framework_TestCase {
1616

1717
use Codeception\Specify;
@@ -27,18 +27,18 @@ class UserTest extends PHPUnit_Framework_TestCase {
2727

2828
$this->specify("username is required", function() {
2929
$this->user->username = null;
30-
verify($user->validate(['username'])->false());
30+
verify($this->user->validate(['username'])->false());
3131
});
3232

3333
$this->specify("username is too long", function() {
3434
$this->user->username = 'toolooooongnaaaaaaameeee',
35-
verify($user->validate(['username'])->false());
35+
verify($this->user->validate(['username'])->false());
3636
});
3737

3838
// alternative, TDD assertions can be used too.
3939
$this->specify("username is ok", function() {
4040
$this->user->username = 'davert',
41-
$this->assertTrue($user->validate(['username']));
41+
$this->assertTrue($this->user->validate(['username']));
4242
});
4343
}
4444
}
@@ -77,7 +77,7 @@ $this->assertTrue(true);
7777
?>
7878
```
7979

80-
If a test fails you will specification text in the result.
80+
If a test fails you will see specification text in the result.
8181

8282
## Isolation
8383

@@ -185,7 +185,7 @@ In both cases, you can optionally test the exception message
185185
<?php
186186

187187
$this->specify('some exception with a message', function() {
188-
throw new NotFoundException("my error message');
188+
throw new NotFoundException('my error message');
189189
}, ['throws' => ['NotFoundException', 'my error message']]);
190190
?>
191191
```
@@ -245,7 +245,7 @@ Install with Composer:
245245

246246
}
247247
```
248-
Include `Codeception\Specifiy` trait into your test.
248+
Include `Codeception\Specify` trait into your test.
249249

250250

251251
License: MIT

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "codeception/specify",
33
"description": "BDD code blocks for PHPUnit and Codeception",
44
"minimum-stability": "stable",
5+
"license": "MIT",
56
"authors": [
67
{
78
"name": "Michael Bodnarchuk",

phpunit.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<phpunit colors="true">
1+
<phpunit colors="true"
2+
bootstrap="tests/_bootstrap.php">
23
<testsuites>
34
<testsuite name="Specify">
45
<directory>tests</directory>

src/Codeception/Specify.php

Lines changed: 113 additions & 26 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,23 +29,29 @@ private function specifyInit()
2729
if (!$this->specifyConfig) $this->specifyConfig = Config::create();
2830
}
2931

30-
function specify($specification, \Closure $callable = null, $params = [])
31-
{
32+
function specify($specification, \Closure $callable = null, $params = [])
33+
{
3234
if (!$callable) return;
3335
$this->specifyInit();
3436

3537
$test = $callable->bindTo($this);
36-
$name = $this->getName();
37-
$this->setName($this->getName().' | '.$specification);
38+
$oldName = $this->getName();
39+
$newName = $oldName . ' | ' . $specification;
40+
41+
$this->setName($newName);
3842

39-
$properties = get_object_vars($this);
43+
$properties = $this->getSpecifyObjectProperties();
4044

4145
// prepare for execution
4246
$throws = $this->getSpecifyExpectedException($params);
4347
$examples = $this->getSpecifyExamples($params);
48+
$showExamplesIndex = $examples !== [[]];
4449

4550
foreach ($examples as $idx => $example) {
46-
$this->setName($name.' | '.$specification .' | examples index '. $idx);
51+
if ($showExamplesIndex) {
52+
$this->setName($newName . ' | examples index ' . $idx);
53+
}
54+
4755
// copy current object properties
4856
$this->specifyCloneProperties($properties);
4957

@@ -52,13 +60,12 @@ function specify($specification, \Closure $callable = null, $params = [])
5260
if ($closure instanceof \Closure) $closure->__invoke();
5361
}
5462
}
63+
5564
$this->specifyExecute($test, $throws, $example);
5665

5766
// restore object properties
58-
foreach ($properties as $property => $val) {
59-
if ($this->specifyConfig->propertyIgnored($property)) continue;
60-
$this->$property = $val;
61-
}
67+
$this->specifyRestoreProperties($properties);
68+
6269
if (!empty($this->afterSpecify) && is_array($this->afterSpecify)) {
6370
foreach ($this->afterSpecify as $closure) {
6471
if ($closure instanceof \Closure) $closure->__invoke();
@@ -67,8 +74,8 @@ function specify($specification, \Closure $callable = null, $params = [])
6774
}
6875

6976
// restore test name
70-
$this->setName($name);
71-
}
77+
$this->setName($oldName);
78+
}
7279

7380
/**
7481
* @param $params
@@ -114,8 +121,10 @@ private function specifyExecute($test, $throws = false, $examples = array())
114121
}
115122

116123
$result = $this->getTestResultObject();
124+
117125
try {
118126
call_user_func_array($test, $examples);
127+
$this->specifyCheckMockObjects();
119128
} catch (\PHPUnit_Framework_AssertionFailedError $e) {
120129
if ($throws !== get_class($e)){
121130
$result->addFailure(clone($this), $e, $result->time());
@@ -173,29 +182,107 @@ function cleanSpecify()
173182
}
174183

175184
/**
176-
* @param $properties
177-
* @return array
185+
* @param ObjectProperty[] $properties
178186
*/
179187
private function specifyCloneProperties($properties)
180188
{
181-
foreach ($properties as $property => $val) {
182-
if ($this->specifyConfig->propertyIgnored($property)) {
183-
continue;
184-
}
185-
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)) {
186194
continue;
187195
}
188196

189-
if ($this->specifyConfig->propertyIsShallowCloned($property)) {
190-
if (is_object($val)) {
191-
$this->$property = clone $val;
197+
if ($this->specifyConfig->propertyIsShallowCloned($propertyName)) {
198+
if (is_object($propertyValue)) {
199+
$property->setValue(clone $propertyValue);
192200
} else {
193-
$this->$property = $val;
201+
$property->setValue($propertyValue);
194202
}
195203
}
196-
if ($this->specifyConfig->propertyIsDeeplyCloned($property)) {
197-
$this->$property = $this->copier->copy($val);
204+
205+
if ($this->specifyConfig->propertyIsDeeplyCloned($propertyName)) {
206+
$property->setValue($this->copier->copy($propertyValue));
207+
}
208+
}
209+
}
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+
$objectReflection = new \ReflectionObject($this);
227+
$propertiesToClone = $objectReflection->getProperties();
228+
229+
if (($classProperties = $this->specifyGetClassPrivateProperties()) !== []) {
230+
$propertiesToClone = array_merge($propertiesToClone, $classProperties);
231+
}
232+
233+
$properties = [];
234+
235+
foreach ($propertiesToClone as $property) {
236+
if ($this->specifyConfig->propertyIgnored($property->getName())) {
237+
continue;
198238
}
239+
240+
$properties[] = new ObjectProperty($this, $property);
241+
}
242+
243+
// isolate mockObjects property from PHPUnit_Framework_TestCase
244+
if (($phpUnitReflection = $this->specifyGetPhpUnitReflection()) !== null) {
245+
$properties[] = $mockObjects = new ObjectProperty(
246+
$this, $phpUnitReflection->getProperty('mockObjects')
247+
);
248+
249+
// remove all mock objects inherited from parent scope(s)
250+
$mockObjects->setValue([]);
251+
}
252+
253+
return $properties;
254+
}
255+
256+
private function specifyCheckMockObjects()
257+
{
258+
if (($phpUnitReflection = $this->specifyGetPhpUnitReflection()) !== null) {
259+
$verifyMockObjects = $phpUnitReflection->getMethod('verifyMockObjects');
260+
$verifyMockObjects->setAccessible(true);
261+
$verifyMockObjects->invoke($this);
262+
}
263+
}
264+
265+
private function specifyGetClassPrivateProperties()
266+
{
267+
static $properties = [];
268+
269+
if (!isset($properties[__CLASS__])) {
270+
$reflection = new \ReflectionClass(__CLASS__);
271+
272+
$properties[__CLASS__] = (get_class($this) !== __CLASS__)
273+
? $reflection->getProperties(\ReflectionProperty::IS_PRIVATE) : [];
274+
}
275+
276+
return $properties[__CLASS__];
277+
}
278+
279+
/**
280+
* @return \ReflectionClass|null
281+
*/
282+
private function specifyGetPhpUnitReflection()
283+
{
284+
if ($this instanceof \PHPUnit_Framework_TestCase) {
285+
return new \ReflectionClass('\PHPUnit_Framework_TestCase');
199286
}
200287
}
201288
}
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/ConfigTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<?php
2-
require_once __DIR__.'/../vendor/autoload.php';
32

4-
class ConfigTest extends \PHPUnit_Framework_TestCase
3+
class ConfigTest extends \SpecifyUnitTest
54
{
65
/**
76
* @var \Codeception\Specify\Config

0 commit comments

Comments
 (0)