33
44use Codeception \Specify \Config ;
55use 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}
0 commit comments