1
|
|
|
<?php |
2
|
|
|
namespace Agavi\Testing; |
3
|
|
|
|
4
|
|
|
// +---------------------------------------------------------------------------+ |
5
|
|
|
// | This file is part of the Agavi package. | |
6
|
|
|
// | Copyright (c) 2005-2011 the Agavi Project. | |
7
|
|
|
// | | |
8
|
|
|
// | For the full copyright and license information, please view the LICENSE | |
9
|
|
|
// | file that was distributed with this source code. You can also view the | |
10
|
|
|
// | LICENSE file online at http://www.agavi.org/LICENSE.txt | |
11
|
|
|
// | vi: set noexpandtab: | |
12
|
|
|
// | Local Variables: | |
13
|
|
|
// | indent-tabs-mode: t | |
14
|
|
|
// | End: | |
15
|
|
|
// +---------------------------------------------------------------------------+ |
16
|
|
|
use Agavi\Controller\Controller; |
17
|
|
|
use Agavi\Core\Context; |
18
|
|
|
use Agavi\Dispatcher\ExecutionContainer; |
19
|
|
|
use Agavi\Dispatcher\OutputType; |
20
|
|
|
use Agavi\Filter\ExecutionFilter; |
21
|
|
|
use Agavi\Request\RequestDataHolder; |
22
|
|
|
use Agavi\Util\Toolkit; |
23
|
|
|
use Agavi\View\View; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* FragmentTestCase is the base class for all fragment tests and provides |
27
|
|
|
* the necessary assertions |
28
|
|
|
* |
29
|
|
|
* |
30
|
|
|
* @package agavi |
31
|
|
|
* @subpackage testing |
32
|
|
|
* |
33
|
|
|
* @author Felix Gilcher <[email protected]> |
34
|
|
|
* @copyright The Agavi Project |
35
|
|
|
* |
36
|
|
|
* @since 1.0.0 |
37
|
|
|
* |
38
|
|
|
* @version $Id$ |
39
|
|
|
*/ |
40
|
|
|
abstract class FragmentTestCase extends PhpUnitTestCase implements FragmentTestCaseInterface |
41
|
|
|
{ |
42
|
|
|
/** |
43
|
|
|
* @var string the name of the context to use, null for default context |
44
|
|
|
*/ |
45
|
|
|
protected $contextName = null; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var string the name of the controller to test |
49
|
|
|
*/ |
50
|
|
|
protected $controllerName; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var string the name of the module |
54
|
|
|
*/ |
55
|
|
|
protected $moduleName; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @var bool the result of the validation process |
59
|
|
|
*/ |
60
|
|
|
protected $validationSuccess; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @var ExecutionContainer the container to run the controller in |
64
|
|
|
*/ |
65
|
|
|
protected $container; |
66
|
|
|
|
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Constructs a test case with the given name. |
70
|
|
|
* |
71
|
|
|
* @param string $name |
72
|
|
|
* @param array $data |
73
|
|
|
* @param string $dataName |
74
|
|
|
*/ |
75
|
|
|
public function __construct($name = null, array $data = array(), $dataName = '') |
76
|
|
|
{ |
77
|
|
|
parent::__construct($name, $data, $dataName); |
78
|
|
|
$this->setRunTestInSeparateProcess(true); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* creates a new ExecutionContainer for each test |
84
|
|
|
* |
85
|
|
|
* @return void |
86
|
|
|
* |
87
|
|
|
* @author Felix Gilcher <[email protected]> |
88
|
|
|
* @since 1.0.0 |
89
|
|
|
*/ |
90
|
|
|
public function setUp() |
91
|
|
|
{ |
92
|
|
|
$this->container = $this->createExecutionContainer(); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* unsets the ExecutionContainer after each test |
98
|
|
|
* |
99
|
|
|
* @return void |
100
|
|
|
* |
101
|
|
|
* @author Felix Gilcher <[email protected]> |
102
|
|
|
* @since 1.0.0 |
103
|
|
|
*/ |
104
|
|
|
public function tearDown() |
105
|
|
|
{ |
106
|
|
|
$this->container = null; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Return the context defined for this test (or the default one). |
111
|
|
|
* |
112
|
|
|
* @return Context The context instance defined for this test. |
113
|
|
|
* |
114
|
|
|
* @author Felix Gilcher <[email protected]> |
115
|
|
|
* @since 1.0.0 |
116
|
|
|
*/ |
117
|
|
|
public function getContext() |
118
|
|
|
{ |
119
|
|
|
return Context::getInstance($this->contextName); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* normalizes a viewname according to the configured rules |
124
|
|
|
* |
125
|
|
|
* Please do not use this method, it exists only for internal |
126
|
|
|
* purposes and will be removed ASAP. You have been warned |
127
|
|
|
* |
128
|
|
|
* @param string $shortName the short view name |
129
|
|
|
* |
130
|
|
|
* @return string the full view name |
131
|
|
|
* |
132
|
|
|
* @author Felix Gilcher <[email protected]> |
133
|
|
|
* @since 1.0.0 |
134
|
|
|
*/ |
135
|
|
|
protected function normalizeViewName($shortName) |
136
|
|
|
{ |
137
|
|
|
if ($shortName !== View::NONE) { |
138
|
|
|
$shortName = Toolkit::evaluateModuleDirective( |
139
|
|
|
$this->moduleName, |
140
|
|
|
'agavi.view.name', |
141
|
|
|
array( |
142
|
|
|
'controllerName' => $this->controllerName, |
143
|
|
|
'viewName' => $shortName, |
144
|
|
|
) |
145
|
|
|
); |
146
|
|
|
$shortName = Toolkit::canonicalName($shortName); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
return $shortName; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* create an executionfilter for the test |
154
|
|
|
* |
155
|
|
|
* the configured executionfilter class will be wrapped in a testing |
156
|
|
|
* extension to provide advanced capabilities required for testing |
157
|
|
|
* only |
158
|
|
|
* |
159
|
|
|
* @return ExecutionFilter |
160
|
|
|
* |
161
|
|
|
* @author Felix Gilcher <[email protected]> |
162
|
|
|
* @since 1.0.0 |
163
|
|
|
*/ |
164
|
|
|
protected function createExecutionFilter() |
165
|
|
|
{ |
166
|
|
|
$effi = $this->getContext()->getFactoryInfo('execution_filter'); |
167
|
|
|
|
168
|
|
|
$wrapper_class = $effi['class'].'UnitTesting'; |
169
|
|
|
|
170
|
|
|
// Grab the actual class name |
171
|
|
View Code Duplication |
if (($pos = strrpos($wrapper_class, '\\')) !== false) { |
|
|
|
|
172
|
|
|
$wrapper_class = substr($wrapper_class, $pos+1); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
//extend the original class to overwrite runController, so that the containers request data is cloned |
176
|
|
View Code Duplication |
if (!class_exists($wrapper_class)) { |
|
|
|
|
177
|
|
|
$code = sprintf('class %1$s extends %2$s |
178
|
|
|
{ |
179
|
|
|
protected $validationResult = null; |
180
|
|
|
|
181
|
|
|
public function executeView(Agavi\Dispatcher\ExecutionContainer $container) |
182
|
|
|
{ |
183
|
|
|
$container->initRequestData(); |
184
|
|
|
return parent::executeView($container); |
185
|
|
|
} |
186
|
|
|
}', |
187
|
|
|
$wrapper_class, |
188
|
|
|
$effi['class']); |
189
|
|
|
|
190
|
|
|
eval($code); |
|
|
|
|
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
// create a new execution container with the wrapped class |
194
|
|
|
$filter = new $wrapper_class(); |
195
|
|
|
$filter->initialize($this->getContext(), $effi['parameters']); |
196
|
|
|
return $filter; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* create an ExecutionContainer for the test |
201
|
|
|
* |
202
|
|
|
* the configured ExecutionContainer class will be wrapped in a testing |
203
|
|
|
* extension to provide advanced capabilities required for testing |
204
|
|
|
* only |
205
|
|
|
* |
206
|
|
|
* @return ExecutionContainer |
207
|
|
|
* |
208
|
|
|
* @author Felix Gilcher <[email protected]> |
209
|
|
|
* @since 1.0.0 |
210
|
|
|
*/ |
211
|
|
|
protected function createExecutionContainer($arguments = null, $outputType = null, $requestMethod = null) |
212
|
|
|
{ |
213
|
|
|
$context = $this->getContext(); |
214
|
|
|
|
215
|
|
|
$ecfi = $context->getFactoryInfo('execution_container'); |
216
|
|
|
$wrapper_class = $ecfi['class'].'UnitTesting'; |
217
|
|
|
|
218
|
|
|
// Grab the actual class name |
219
|
|
View Code Duplication |
if (($pos = strrpos($wrapper_class, '\\')) !== false) { |
|
|
|
|
220
|
|
|
$wrapper_class = substr($wrapper_class, $pos+1); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
//extend the original class to add a setter for the controller instance |
224
|
|
View Code Duplication |
if (!class_exists($wrapper_class)) { |
|
|
|
|
225
|
|
|
$code = sprintf('class %1$s extends %2$s |
226
|
|
|
{ |
227
|
|
|
|
228
|
|
|
public function setControllerInstance(Agavi\Controller\Controller $controller) |
229
|
|
|
{ |
230
|
|
|
$this->controllerInstance = $controller; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
public function initRequestData() |
234
|
|
|
{ |
235
|
|
|
parent::initRequestData(); |
236
|
|
|
} |
237
|
|
|
}', |
238
|
|
|
$wrapper_class, |
239
|
|
|
$ecfi['class']); |
240
|
|
|
|
241
|
|
|
eval($code); |
|
|
|
|
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
$ecfi['class'] = $wrapper_class; |
245
|
|
|
$context->setFactoryInfo('execution_container', $ecfi); |
246
|
|
|
|
247
|
|
|
if (!($arguments instanceof RequestDataHolder)) { |
248
|
|
|
$arguments = $this->createRequestDataHolder(array(RequestDataHolder::SOURCE_PARAMETERS => $arguments)); |
249
|
|
|
} |
250
|
|
|
// create a new execution container with the wrapped class |
251
|
|
|
$container = $context->getDispatcher()->createExecutionContainer($this->moduleName, $this->controllerName, $arguments, $outputType, $requestMethod); |
252
|
|
|
|
253
|
|
|
return $container; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* creates an Controller instance and initializes it with this testcases |
258
|
|
|
* container |
259
|
|
|
* |
260
|
|
|
* @return Controller |
261
|
|
|
* |
262
|
|
|
* @author Felix Gilcher <[email protected]> |
263
|
|
|
* @since 1.0.0 |
264
|
|
|
*/ |
265
|
|
|
protected function createControllerInstance() |
266
|
|
|
{ |
267
|
|
|
$controllerInstance = $this->getContext()->getDispatcher()->createControllerInstance($this->moduleName, $this->controllerName); |
268
|
|
|
$controllerInstance->initialize($this->container); |
269
|
|
|
return $controllerInstance; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* create a requestDataHolder with the given arguments and type |
274
|
|
|
* |
275
|
|
|
* arguments need to be passed in the way {@see AgaviRequestDataHolder} accepts them |
276
|
|
|
* |
277
|
|
|
* array(AgaviRequestDataHolder::SOURCE_PARAMETERS => array('foo' => 'bar')) |
278
|
|
|
* |
279
|
|
|
* if no type is passed, the default for the configured request class will be used |
280
|
|
|
* |
281
|
|
|
* @param array a two-dimensional array with the arguments |
282
|
|
|
* @param string the subclass of AgaviRequestDataHolder to create |
283
|
|
|
* |
284
|
|
|
* @return RequestDataHolder |
285
|
|
|
* |
286
|
|
|
* @author Felix Gilcher <[email protected]> |
287
|
|
|
* @since 1.0.0 |
288
|
|
|
*/ |
289
|
|
|
protected function createRequestDataHolder(array $arguments = array(), $type = null) |
290
|
|
|
{ |
291
|
|
|
if (null === $type) { |
292
|
|
|
$type = $this->getContext()->getRequest()->getParameter('request_data_holder_class', 'AgaviRequestDataHolder'); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
$class = new $type($arguments); |
296
|
|
|
return $class; |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* assert that the exectionContainer has a given attribute with the expected value |
302
|
|
|
* |
303
|
|
|
* @param mixed $expected the expected attribute value |
304
|
|
|
* @param string $attributeName the attribute name |
305
|
|
|
* @param string $namespace the attribute namespace |
306
|
|
|
* @param string $message an optional message to display if the test fails |
307
|
|
|
* @param float $delta |
308
|
|
|
* @param integer $maxDepth |
309
|
|
|
* @param boolean $canonicalizeEol |
310
|
|
|
* |
311
|
|
|
* @see PHPUnit_Framework_Assert::assertEquals() |
312
|
|
|
* |
313
|
|
|
* @author Felix Gilcher <[email protected]> |
314
|
|
|
* @since 1.0.0 |
315
|
|
|
*/ |
316
|
|
|
protected function assertContainerAttributeEquals($expected, $attributeName, $namespace = null, $message = 'Failed asserting that the attribute <%1$s/%2$s> has the value <%3$s>', $delta = 0.0, $maxDepth = 10, $canonicalizeEol = false) |
317
|
|
|
{ |
318
|
|
|
$this->assertEquals($expected, $this->container->getAttribute($attributeName, $namespace), sprintf($message, $namespace, $attributeName, $expected), $delta, $maxDepth, $canonicalizeEol); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* assert that the executionContainer has a given attribute |
323
|
|
|
* |
324
|
|
|
* @param string $attributeName the attribute name |
325
|
|
|
* @param string $namespace the attribute namespace |
326
|
|
|
* @param string $message an optional message to display if the test fails |
327
|
|
|
* |
328
|
|
|
* @author Felix Gilcher <[email protected]> |
329
|
|
|
* @since 1.0.0 |
330
|
|
|
*/ |
331
|
|
|
protected function assertContainerAttributeExists($attributeName, $namespace = null, $message = 'Failed asserting that the container has an attribute named <%1$s/%2$s>.') |
332
|
|
|
{ |
333
|
|
|
$this->assertTrue($this->container->hasAttribute($attributeName, $namespace), sprintf($message, $namespace, $attributeName)); |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/* --- container delegates --- */ |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* @see AgaviExcutionContainer::setOutputType() |
340
|
|
|
* |
341
|
|
|
* @author Felix Gilcher <[email protected]> |
342
|
|
|
* @since 1.0.0 |
343
|
|
|
*/ |
344
|
|
|
protected function setOutputType(OutputType $outputType) |
345
|
|
|
{ |
346
|
|
|
$this->container->setOutputType($outputType); |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* @see AgaviRequest::setRequestData() |
351
|
|
|
* |
352
|
|
|
* @author Felix Gilcher <[email protected]> |
353
|
|
|
* @since 1.0.0 |
354
|
|
|
*/ |
355
|
|
|
protected function setRequestData(RequestDataHolder $rd) |
356
|
|
|
{ |
357
|
|
|
$this->container->setRequestData($rd); |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* @see AgaviExcutionContainer::setArguments() |
362
|
|
|
* |
363
|
|
|
* @author Felix Gilcher <[email protected]> |
364
|
|
|
* @since 1.0.0 |
365
|
|
|
*/ |
366
|
|
|
protected function setArguments(RequestDataHolder $rd) |
367
|
|
|
{ |
368
|
|
|
$this->container->setArguments($rd); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* @see AgaviExcutionContainer::setRequestMethod() |
373
|
|
|
* |
374
|
|
|
* @author Felix Gilcher <[email protected]> |
375
|
|
|
* @since 1.0.0 |
376
|
|
|
*/ |
377
|
|
|
protected function setRequestMethod($method) |
378
|
|
|
{ |
379
|
|
|
$this->container->setRequestMethod($method); |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* @see AgaviAttributeHolder::clearAttributes() |
384
|
|
|
* |
385
|
|
|
* @author Felix Gilcher <[email protected]> |
386
|
|
|
* @since 1.0.0 |
387
|
|
|
*/ |
388
|
|
|
protected function clearAttributes() |
389
|
|
|
{ |
390
|
|
|
$this->container->clearAttributes(); |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* @see AgaviAttributeHolder::getAttribute() |
395
|
|
|
* |
396
|
|
|
* @author Felix Gilcher <[email protected]> |
397
|
|
|
* @since 1.0.0 |
398
|
|
|
*/ |
399
|
|
|
protected function &getAttribute($name, $default = null) |
400
|
|
|
{ |
401
|
|
|
return $this->container->getAttribute($name, null, $default); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
/** |
405
|
|
|
* @see AgaviAttributeHolder::getAttributeNames() |
406
|
|
|
* |
407
|
|
|
* @author Felix Gilcher <[email protected]> |
408
|
|
|
* @since 1.0.0 |
409
|
|
|
*/ |
410
|
|
|
protected function getAttributeNames() |
411
|
|
|
{ |
412
|
|
|
return $this->container->getAttributeNames(); |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
/** |
416
|
|
|
* @see AgaviAttributeHolder::getAttributes() |
417
|
|
|
* |
418
|
|
|
* @author Felix Gilcher <[email protected]> |
419
|
|
|
* @since 1.0.0 |
420
|
|
|
*/ |
421
|
|
|
protected function &getAttributes() |
422
|
|
|
{ |
423
|
|
|
return $this->container->getAttributes(); |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
/** |
427
|
|
|
* @see AgaviAttributeHolder::hasAttribute() |
428
|
|
|
* |
429
|
|
|
* @author Felix Gilcher <[email protected]> |
430
|
|
|
* @since 1.0.0 |
431
|
|
|
*/ |
432
|
|
|
protected function hasAttribute($name) |
433
|
|
|
{ |
434
|
|
|
return $this->container->hasAttribute($name); |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
/** |
438
|
|
|
* @see AgaviAttributeHolder::removeAttribute() |
439
|
|
|
* |
440
|
|
|
* @author Felix Gilcher <[email protected]> |
441
|
|
|
* @since 1.0.0 |
442
|
|
|
*/ |
443
|
|
|
protected function &removeAttribute($name) |
444
|
|
|
{ |
445
|
|
|
return $this->container->removeAttribute($name); |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
/** |
449
|
|
|
* @see AgaviAttributeHolder::setAttribute() |
450
|
|
|
* |
451
|
|
|
* @author Felix Gilcher <[email protected]> |
452
|
|
|
* @since 1.0.0 |
453
|
|
|
*/ |
454
|
|
|
protected function setAttribute($name, $value) |
455
|
|
|
{ |
456
|
|
|
$this->container->setAttribute($name, $value); |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* @see AgaviAttributeHolder::appendAttribute() |
461
|
|
|
* |
462
|
|
|
* @author Felix Gilcher <[email protected]> |
463
|
|
|
* @since 1.0.0 |
464
|
|
|
*/ |
465
|
|
|
protected function appendAttribute($name, $value) |
466
|
|
|
{ |
467
|
|
|
$this->container->appendAttribute($name, $value); |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
/** |
471
|
|
|
* @see AgaviAttributeHolder::setAttributesByRef() |
472
|
|
|
* |
473
|
|
|
* @author Felix Gilcher <[email protected]> |
474
|
|
|
* @since 1.0.0 |
475
|
|
|
*/ |
476
|
|
|
protected function setAttributeByRef($name, &$value) |
477
|
|
|
{ |
478
|
|
|
$this->container->setAttributeByRef($name, $value); |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
/** |
482
|
|
|
* @see AgaviAttributeHolder::appendAttributeByRef() |
483
|
|
|
* |
484
|
|
|
* @author Felix Gilcher <[email protected]> |
485
|
|
|
* @since 1.0.0 |
486
|
|
|
*/ |
487
|
|
|
protected function appendAttributeByRef($name, &$value) |
488
|
|
|
{ |
489
|
|
|
$this->container->appendAttributeByRef($name, $value); |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
/** |
493
|
|
|
* @see AgaviAttributeHolder::setAttributes() |
494
|
|
|
* |
495
|
|
|
* @author Felix Gilcher <[email protected]> |
496
|
|
|
* @since 1.0.0 |
497
|
|
|
*/ |
498
|
|
|
protected function setAttributes(array $attributes) |
499
|
|
|
{ |
500
|
|
|
$this->container->setAttributes($attributes); |
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
/** |
504
|
|
|
* @see AgaviAttributeHolder::setAttributesByRef() |
505
|
|
|
* |
506
|
|
|
* @author Felix Gilcher <[email protected]> |
507
|
|
|
* @since 1.0.0 |
508
|
|
|
*/ |
509
|
|
|
protected function setAttributesByRef(array &$attributes) |
510
|
|
|
{ |
511
|
|
|
$this->container->setAttributesByRef($attributes); |
512
|
|
|
} |
513
|
|
|
} |
514
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.