├── DataFixtures ├── AbstractFixtureCreate.php └── AbstractFixtureUpdate.php ├── ICBaseTestBundle.php ├── README.md ├── Test ├── BundleTestCase.php ├── ContainerAwareTestCase.php ├── DependencyInjection │ ├── ConfigurationTestCase.php │ └── ExtensionTestCase.php ├── Dummy │ └── FunctionProxy.php ├── Functional │ └── WebTestCase.php ├── Helper │ ├── AbstractHelper.php │ ├── CommandHelper.php │ ├── ControllerHelper.php │ ├── HelperInterface.php │ ├── PersistenceHelper.php │ ├── RouteHelper.php │ ├── ServiceHelper.php │ ├── SessionHelper.php │ ├── Unit │ │ ├── EntityHelper.php │ │ ├── FunctionHelper.php │ │ └── UnitHelper.php │ └── ValidatorHelper.php ├── Loader │ ├── FixtureLoader.php │ ├── PostLoadSubscriberInterface.php │ ├── PreLoadSubscriberInterface.php │ └── SchemaLoader.php ├── Model │ └── JsonResponse.php ├── TestCase.php ├── Validator │ └── ValidatorTestCase.php └── WebTestCase.php └── composer.json /DataFixtures/AbstractFixtureCreate.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | abstract class AbstractFixtureCreate extends AbstractFixture 17 | { 18 | /** 19 | * Creates and persists the entities provided from the data list 20 | * 21 | * @param \Doctrine\Common\Persistence\ObjectManager $manager 22 | */ 23 | public function load(ObjectManager $manager) 24 | { 25 | foreach ($this->getDataList() as $referenceKey => $data) { 26 | if ($this->hasReference($referenceKey)) { 27 | continue; 28 | } 29 | 30 | $entity = $this->buildEntity($data); 31 | 32 | if ( ! $entity) { 33 | continue; 34 | } 35 | 36 | $manager->persist($entity); 37 | 38 | if (is_int($referenceKey)) { 39 | continue; 40 | } 41 | 42 | $this->setReference($referenceKey, $entity); 43 | } 44 | 45 | $manager->flush(); 46 | } 47 | 48 | /** 49 | * Build an entity 50 | * 51 | * @param array $data 52 | * 53 | * @return \IC\Bundle\Base\ComponentBundle\Entity 54 | */ 55 | abstract protected function buildEntity($data); 56 | 57 | /** 58 | * Retrieve the data list of entities 59 | * 60 | * @return array 61 | */ 62 | abstract protected function getDataList(); 63 | } 64 | -------------------------------------------------------------------------------- /DataFixtures/AbstractFixtureUpdate.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | abstract class AbstractFixtureUpdate extends AbstractFixture 17 | { 18 | /** 19 | * Updates and persists the entities provided from the data list 20 | * 21 | * @param \Doctrine\Common\Persistence\ObjectManager $manager 22 | * 23 | * @throws \InvalidArgumentException If invalid reference key to update was provided 24 | */ 25 | public function load(ObjectManager $manager) 26 | { 27 | foreach ($this->getDataList() as $referenceKey => $data) { 28 | if ( ! $this->hasReference($referenceKey)) { 29 | throw new \InvalidArgumentException('Reference ['. $referenceKey .'] does not exist'); 30 | } 31 | 32 | $entity = $this->updateEntity($data, $this->getReference($referenceKey)); 33 | 34 | if ( ! $entity) { 35 | continue; 36 | } 37 | 38 | $manager->persist($entity); 39 | 40 | $this->setReference($referenceKey, $entity); 41 | } 42 | 43 | $manager->flush(); 44 | } 45 | 46 | /** 47 | * Update an entity 48 | * 49 | * @param array $data 50 | * @param object $entity 51 | * 52 | * @return object 53 | */ 54 | abstract protected function updateEntity($data, $entity); 55 | 56 | /** 57 | * Retrieve the data list of entities 58 | * 59 | * @return array 60 | */ 61 | abstract protected function getDataList(); 62 | } 63 | -------------------------------------------------------------------------------- /ICBaseTestBundle.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class ICBaseTestBundle extends Bundle 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InstaClick Base Test Bundle 2 | 3 | *IMPORTANT NOTICE:* This bundle is still under development. Any changes will 4 | be done without prior notice to consumers of this package. Of course this 5 | code will become stable at a certain point, but for now, use at your own risk. 6 | 7 | ## Introduction 8 | 9 | This bundle provides lower level support for unit and functional tests on 10 | Symfony2. Through the concept of helpers and loaders, this bundle supports 11 | individual support for test types, such as Command, Controller, Service, 12 | Validator, etc. 13 | 14 | This bundle requires that you are using, at least, Symfony 2.1. 15 | 16 | ## Installation 17 | 18 | Installing this bundle can be done through these simple steps: 19 | 20 | 1. Add this bundle to your project as a composer dependency: 21 | ```javascript 22 | // composer.json 23 | { 24 | // ... 25 | require-dev: { 26 | // ... 27 | "instaclick/base-test-bundle": "dev-master" 28 | } 29 | } 30 | ``` 31 | 32 | 2. Add this bundle in your application kernel: 33 | ```php 34 | // application/ApplicationKernel.php 35 | public function registerBundles() 36 | { 37 | // ... 38 | if (in_array($this->getEnvironment(), array('test'))) { 39 | $bundles[] = new IC\Bundle\Base\TestBundle\ICBaseTestBundle(); 40 | } 41 | 42 | return $bundles; 43 | } 44 | ``` 45 | 46 | 3. Double check if your session name is configured correctly: 47 | ```yaml 48 | # application/config/config_test.yml 49 | framework: 50 | test: ~ 51 | session: 52 | name: "myapp" 53 | ``` 54 | 55 | ## Unit Testing 56 | 57 | By default, Symfony2 does not provide a native customized support for unit test 58 | creation. To mitigate this problem, this bundle contains a wide set of basic 59 | unit test abstraction to help you with this job. 60 | 61 | ### Protected/Private 62 | 63 | There may be times where you want to directly test a protected/private method 64 | or access a non-public property (and the class lacks a getter or setter). 65 | For example, the call chain from the closest public method is sufficiently 66 | long to make testing an arduous task. 67 | 68 | To overcome this obstacle, TestCase provides some methods to assist you. 69 | 70 | Let's say this is your subject under test: 71 | 72 | ```php 73 | class Foo 74 | { 75 | protected $bar; 76 | 77 | private function getBar() 78 | { 79 | return $this->bar; 80 | } 81 | } 82 | ``` 83 | 84 | Here is an example: 85 | 86 | ```php 87 | use IC\Bundle\Base\TestBundle\Test\TestCase; 88 | 89 | class ICFooBarBundleTest extends TestCase 90 | { 91 | public function testGetBar() 92 | { 93 | $subject = new Foo; 94 | $expected = 'Hello'; 95 | 96 | $this->setPropertyOnObject($subject, 'bar', $expected); 97 | 98 | $method = $this->makeCallable($subject, 'getBar'); 99 | 100 | $this->assertEquals($expected, $method->invoke($subject)); 101 | } 102 | } 103 | ``` 104 | 105 | ### Bundle testing 106 | 107 | Most people do not even think about testing a bundle initialization. This is a 108 | bad concept, because every line of code deserves to be tested, even though you 109 | may not have manually created a class. 110 | 111 | Bundle classes are known to be the place to register your CompilerPass 112 | instances. No matter if you have a CompilerPass or not, it is a good practice 113 | to create a default test for your Bundle class. 114 | Here is an example on how to achieve it: 115 | 116 | ```php 117 | use IC\Bundle\Base\TestBundle\Test\BundleTestCase; 118 | use IC\Bundle\Base\MailBundle\ICBaseMailBundle; 119 | 120 | class ICBaseMailBundleTest extends BundleTestCase 121 | { 122 | public function testBuild() 123 | { 124 | $bundle = new ICBaseMailBundle(); 125 | 126 | $bundle->build($this->container); 127 | 128 | // Add your tests here 129 | } 130 | } 131 | ``` 132 | 133 | ### Dependency Injection 134 | 135 | #### Configuration testing 136 | 137 | Just like Bundle classes, Configuration classes are very easy to overlook when 138 | testing. Testing this specific test is a good approach because it validates 139 | your line of thought with Bundle configuration normalization of parameters or 140 | even configuration default values. ICBaseTestBundle already provides a small 141 | class that can help you with this task. 142 | 143 | ```php 144 | use IC\Bundle\Base\TestBundle\Test\DependencyInjection\ConfigurationTestCase; 145 | use IC\Bundle\Base\MailBundle\DependencyInjection\Configuration; 146 | 147 | class ConfigurationTest extends ConfigurationTestCase 148 | { 149 | public function testDefaults() 150 | { 151 | $configuration = $this->processConfiguration(new Configuration(), array()); 152 | 153 | $this->assertEquals('INBOX', $configuration['mail_bounce']['mailbox']); 154 | } 155 | 156 | // ... 157 | } 158 | ``` 159 | 160 | #### Extension testing 161 | 162 | Testing the DependencyInjection Extension helps you to validate your service 163 | definitions and container configuration. 164 | Helpful methods available to you: 165 | * `assertAlias($expected, $key)` 166 | * `assertParameter($expected, $key)` 167 | * `assertHasDefinition($id)` 168 | * `assertDICConstructorArguments(Definition $definition, array $arguments)` 169 | * `assertDICDefinitionClass(Definition $definition, $expectedClass)` 170 | * `assertDICDefinitionMethodCallAt($position, Definition $definition, $methodName, array $params = null)` 171 | 172 | ```php 173 | use IC\Bundle\Base\TestBundle\Test\DependencyInjection\ExtensionTestCase; 174 | use IC\Bundle\Base\MailBundle\DependencyInjection\ICBaseMailExtension; 175 | 176 | class ICBaseMailExtensionTest extends ExtensionTestCase 177 | { 178 | /** 179 | * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 180 | */ 181 | public function testInvalidConfiguration() 182 | { 183 | $extension = new ICBaseMailExtension(); 184 | $configuration = array(); 185 | 186 | $this->load($extension, $configuration); 187 | } 188 | 189 | public function testValidConfiguration() 190 | { 191 | $this->createFullConfiguration(); 192 | 193 | $this->assertParameter('John Smith', 'ic_base_mail.composer.default_sender.name'); 194 | 195 | $this->assertDICConstructorArguments( 196 | $this->container->getDefinition('ic_base_mail.service.composer'), 197 | array() 198 | ); 199 | $this->assertDICConstructorArguments( 200 | $this->container->getDefinition('ic_base_mail.service.sender'), 201 | array() 202 | ); 203 | $this->assertDICConstructorArguments( 204 | $this->container->getDefinition('ic_base_mail.service.bounce_mail'), 205 | array() 206 | ); 207 | } 208 | 209 | // ... 210 | } 211 | ``` 212 | 213 | ### Validator testing 214 | 215 | Validators are a key part of the system, because it helps you verify your 216 | business rules are being respected. Testing them becomes even more crucial. 217 | Constraints can generate violations at different locations. In order to help 218 | you verify it assigns it at the correct place, `ValidatorTestCase` provides you 219 | a set of methods: 220 | * `assertValid(ConstraintValidator $validator, Constraint $constraint, $value)` 221 | * `assertInvalid(ConstraintValidator $validator, Constraint $constraint, $value, $message, array $parameters = array())` 222 | * `assertInvalidAtPath(ConstraintValidator $validator, Constraint $constraint, $value, $type, $message, array $parameters = array())` 223 | * `assertInvalidAtSubPath(ConstraintValidator $validator, Constraint $constraint, $value, $type, $message, array $parameters = array())` 224 | 225 | ```php 226 | use MyBundle\Validator\Constraints; 227 | use IC\Bundle\Base\TestBundle\Test\Validator\ValidatorTestCase; 228 | 229 | class BannedEmailValidatorTest extends ValidatorTestCase 230 | { 231 | public function testValid() 232 | { 233 | $validator = new Constraints\BannedEmailValidator(); 234 | $constraint = new Constraints\BannedEmail(); 235 | $value = 'email@domain.com'; 236 | 237 | $this->assertValid($validator, $constraint, $value); 238 | } 239 | 240 | public function testInvalid() 241 | { 242 | $validator = new Constraints\BannedEmailValidator(); 243 | $constraint = new Constraints\BannedEmail(); 244 | $value = 'domain.com'; 245 | $message = 'Please provide a valid email.'; 246 | $parameters = array(); 247 | 248 | $this->assertInvalid($validator, $constraint, $value, $message, $parameters); 249 | } 250 | } 251 | ``` 252 | 253 | ## Creating your first functional test 254 | 255 | Just like a Symfony2 test, implementing a functional test is easy: 256 | 257 | ```php 258 | use IC\Bundle\Base\TestBundle\Test\Functional\WebTestCase; 259 | 260 | class MyFunctionalTest extends WebTestCase 261 | { 262 | public function testSomething() 263 | { 264 | // Normal test here. You can benefit from an already initialized 265 | // Symfony2 Client by using directly $this->getClient() 266 | } 267 | } 268 | ``` 269 | 270 | ## Functional tests that requires Database to be initialized 271 | 272 | When building your functional tests, it is recurrent that you want your test 273 | Database to be created and populated with initial information. This bundle 274 | comes with native support for Doctrine Data Fixtures, which allows you to 275 | load your database information before your test is actually executed. 276 | 277 | To enable your schema to be initialized and also load the initial Database 278 | information, just implement the protected static method `getFixtureList`: 279 | 280 | ```php 281 | /** 282 | * {@inheritdoc} 283 | */ 284 | protected static function getFixtureList() 285 | { 286 | return array( 287 | 'Me\MyBundle\DataFixtures\ORM\LoadData' 288 | ); 289 | } 290 | ``` 291 | 292 | If you don't need any fixtures to be loaded before your test, but still want 293 | your empty Database schema to be loaded, you can tell the TestCase to still 294 | force the schema to be loaded by changing the configuration property flag: 295 | 296 | ```php 297 | protected $forceSchemaLoad = true; 298 | ``` 299 | 300 | ## Overriding the default client instance 301 | 302 | Some applications require more granular control than what Symfony2 Client can do. 303 | This bundle allows you to change the default client initialization, just like 304 | you normally do with your Symfony2 WebTestCase, by overriding the static method 305 | `createClient`. 306 | 307 | ### Changing Client environment 308 | 309 | This bundle allows you to easily change the environment the Client gets 310 | initialized. To change the default environment (default: "test"), just redefine 311 | the constant `ENVIRONMENT`: 312 | 313 | ```php 314 | const ENVIRONMENT = "default"; 315 | ``` 316 | 317 | ### Changing default Object Manager name 318 | 319 | When using Databases, you may want to change the default ObjectManager your 320 | test should run against. Just like Client's environment, changing the default 321 | ObjectManager only requires you to redefine the constant `MANAGER_NAME`: 322 | 323 | ```php 324 | const MANAGER_NAME = "stats"; 325 | ``` 326 | 327 | ### Server authentication 328 | 329 | Whenever your application uses HTTP authentication, your test should still have 330 | an ability to test secured pages. With simplicity in mind, Client can be 331 | initialized in an authenticated state for HTTP. The only required step is 332 | implement the protected static method `getServerParameters`: 333 | 334 | ```php 335 | /** 336 | * {@inheritdoc} 337 | */ 338 | protected static function getServerParameters() 339 | { 340 | return array( 341 | 'PHP_AUTH_USER' => 'admin', 342 | 'PHP_AUTH_PW' => 'jed1*passw0rd' 343 | ); 344 | } 345 | ``` 346 | 347 | Note: this assumes you have enabled `http_basic` in your security configuration using this setting in the `config_test.yml` file: 348 | 349 | ```yaml 350 | # app/config/config_test.yml 351 | security: 352 | firewalls: 353 | your_firewall_name: 354 | http_basic: 355 | ``` 356 | 357 | See [How to simulate HTTP Authentication in a Functional Test](http://symfony.com/doc/current/cookbook/testing/http_authentication.html) for details 358 | 359 | ### Changing Client initialization 360 | 361 | Oftentimes, overriding `createClient` is enough. Whenever you need more 362 | refined support, you can still override the default Client initialization by 363 | overriding the protected static method `initializeClient` (sample is actually 364 | the default implementation of the method): 365 | 366 | ```php 367 | /** 368 | * {@inheritdoc} 369 | */ 370 | protected static function initializeClient() 371 | { 372 | return static::createClient( 373 | array('environment' => static::ENVIRONMENT), 374 | static::getServerParameters() 375 | ); 376 | } 377 | ``` 378 | 379 | ## Useful hints 380 | 381 | ### Creating a class MockBuilder 382 | 383 | Instead of using the native API of PHPUnit, WebTestCase provides a useful 384 | method right at your hands: 385 | 386 | ```php 387 | public function testFoo() 388 | { 389 | $myMock = $this->getClassMock('My\Foo'); 390 | 391 | $myMock->expects($this->any()) 392 | ->method('bar') 393 | ->will($this->returnValue(true)); 394 | 395 | // ... 396 | } 397 | ``` 398 | 399 | ### Retrieving the Service Container 400 | 401 | Symfony Client holds an instance of Service Container. You can retrieve the 402 | container instance directly from the client: 403 | 404 | ```php 405 | public function testFooService() 406 | { 407 | $container = $this->getClient()->getContainer(); 408 | 409 | // ... 410 | } 411 | ``` 412 | 413 | ### Retrieving Database references 414 | 415 | Database dependant applications usually force you to fetch for elements before 416 | actually testing/consuming them. WebTestCase takes advantage of Doctrine Data 417 | Fixtures package, allowing you to retrieve references without requiring a 418 | database fetch. 419 | 420 | ```php 421 | public function testIndex() 422 | { 423 | $credential = $this->getReferenceRepository()->getReference('core.security.credential#admin'); 424 | 425 | // ... 426 | } 427 | ``` 428 | 429 | ## Database dependant functional tests 430 | 431 | In most cases, your application relies on a database to work. To help you on this 432 | task, and also speed up the execution of your suite, we strongly suggest that 433 | you use SQLite as your test database. 434 | The reason why to do that is because this database works around a single file, 435 | allowing you to easily create isolated scenarios. Also, this bundle has an 436 | ability to cache the generated schema and reuse it for every test. 437 | Another piece of functionality: this bundle integrates natively with Doctrine Data 438 | Fixtures, allowing your SQLite test database to be cached with common - test 439 | agnostic - information even before your actual test gets executed. 440 | 441 | Finally, but no less important, if you use SQLite, your test will run 442 | faster with all the native support built-in, simply by using this bundle. 443 | 444 | To use SQLite as your test database, add this to your `app/config_test.yml`: 445 | 446 | ```yaml 447 | doctrine: 448 | dbal: 449 | default_connection: default 450 | connections: 451 | default: 452 | driver: pdo_sqlite 453 | path: %kernel.cache_dir%/test.db 454 | ``` 455 | 456 | **Attention: you need to use Doctrine >= 2.2 to benefit from this feature.** 457 | 458 | ## Using Helpers 459 | 460 | This bundle comes with built-in helpers to simplify testing individual pieces 461 | of software. This section will explain the native ones and also how to inject 462 | your own helper. 463 | 464 | ### Retrieving a helper 465 | 466 | Access helpers by taking advantage of the Symfony2 Client instance available in 467 | WebTestCase. All helpers are registered in the latter, and it allows you to 468 | retrieve an instance easily by calling the method: 469 | 470 | ```php 471 | public function testFoo() 472 | { 473 | $commandHelper = $this->getHelper('command'); 474 | 475 | // ... 476 | } 477 | ``` 478 | 479 | ### Available helpers 480 | 481 | ICBaseTestBundle comes with a collection of helpers out of the box. Here is the 482 | list of available helpers: 483 | 484 | * Command 485 | * Controller 486 | * Persistence 487 | * Route 488 | * Service 489 | * Session 490 | * Validator 491 | * Unit/Entity 492 | * Unit/Function 493 | 494 | #### Command Helper 495 | 496 | This helper is available to you under the name `command`. 497 | It works as a wrapper around the Symfony Console Component. This also allows 498 | you to configure your command, build the command input and retrieve the 499 | response content by using its available API. 500 | As an example, here is a full implementation of a command test: 501 | 502 | ```php 503 | /** 504 | * @dataProvider provideDataForCommand 505 | */ 506 | public function testCommand(array $arguments, $content) 507 | { 508 | $commandHelper = $this->getHelper('command'); 509 | 510 | $commandHelper->setCommandName('ic:base:mail:flush-queue'); 511 | $commandHelper->setMaxMemory(5242880); // Optional (default value: 5 * 1024 * 1024 KB) 512 | 513 | $input = $commandHelper->getInput($arguments); 514 | $response = $commandHelper->run($input); 515 | 516 | $this->assertContains($content, $response); 517 | } 518 | 519 | /** 520 | * {@inheritdoc} 521 | */ 522 | public function provideDataForCommand() 523 | { 524 | return array( 525 | array(array('--server' => 'mail.instaclick.com'), 'Flushed queue successfully'), 526 | ); 527 | } 528 | ``` 529 | 530 | #### Controller Helper 531 | 532 | This helper is available to you under the name `controller`. 533 | The motivation of this helper is to enable sub-requests to be executed without 534 | requiring the master request to be called. It allows you to simulate a request and 535 | check for returned content. 536 | 537 | **IMPORTANT NOTICE:** Controller Helper is still under development. It is part 538 | of the plan to connect Symfony\Component\DomCrawler\Crawler as a separate 539 | method. 540 | 541 | ```php 542 | public function testViewAction() 543 | { 544 | $controllerHelper = $this->getHelper('controller'); 545 | $response = $controllerHelper->render( 546 | 'ICBaseGeographicalBundle:Map:view', 547 | array( 548 | 'attributes' => array( 549 | 'coordinate' => new Coordinate(-34.45, 45.56), 550 | 'width' => 640, 551 | 'height' => 480 552 | ) 553 | ) 554 | ); 555 | 556 | $this->assertContains('-34.45', $response); 557 | $this->assertContains('45.56', $response); 558 | $this->assertContains('640', $response); 559 | $this->assertContains('480', $response); 560 | } 561 | ``` 562 | 563 | #### Persistence Helper 564 | 565 | This helper is available to you under the name `persistence`. 566 | The persistence helper transforms a reference key to a reference object, 567 | or a list of reference keys to a list of reference objects. 568 | 569 | ```php 570 | public function testFoo() 571 | { 572 | $persistenceHelper = $this->getHelper('persistence'); 573 | 574 | $credentialList = $persistenceHelper->transformToReference( 575 | array( 576 | 'core.security.credential#admin', 577 | 'core.security.credential#user', 578 | ) 579 | ); 580 | 581 | ... 582 | } 583 | ``` 584 | 585 | #### Route Helper 586 | 587 | This helper is available to you under the name `route`. 588 | The Route helper provides a method to retrieve a generated route from a route id. 589 | Moreover, if the route is not registered, the test is marked as skipped. 590 | 591 | ```php 592 | /** 593 | * @dataProvider provideRouteData 594 | */ 595 | public function testRoute($routeId, $parameters) 596 | { 597 | $routeHelper = $this->getHelper('route'); 598 | 599 | $route = $routeHelper->getRoute($routeId, $parameters, $absolute = false); 600 | 601 | ... 602 | } 603 | ``` 604 | 605 | #### Service Helper 606 | 607 | This helper is available to you under the name `service`. 608 | Whenever you want to mock a Service and automatically inject it back into Service 609 | Container, this helper is for you. Helper contains a method that does that: 610 | `mock`. It returns an instance of MockBuilder. 611 | 612 | ```php 613 | public function testFoo() 614 | { 615 | $serviceHelper = $this->getHelper('service'); 616 | 617 | $authenticationService = $serviceHelper->mock('core.security.authentication'); 618 | 619 | // ... 620 | } 621 | ``` 622 | 623 | #### Session Helper 624 | 625 | This helper is available to you under the name `session`. 626 | Session helper was written with a simple idea in mind: allow you to simulate 627 | login for controller tests. Of course, Session helper also allows you to 628 | retrieve the actual Symfony Session to define/check/remove entries normally 629 | too. 630 | 631 | ```php 632 | pubic function testIndex() 633 | { 634 | $sessionHelper = $this->getHelper('session'); 635 | 636 | $credential = $this->getReferenceRepository()->getReference('core.security.credential#admin'); 637 | 638 | // $sessionHelper->getSession() is also available 639 | $sessionHelper->authenticate($credential, 'secured_area'); 640 | 641 | // ... 642 | } 643 | ``` 644 | 645 | #### Validator Helper 646 | 647 | This helper is available to you under the name `validator`. 648 | Validator Helper encapsulates more logic than the other native helpers. It 649 | allows you to also test success and error states because it requires internal 650 | mocking of elements needed for testing. 651 | 652 | ```php 653 | public function testSuccessValidate($value) 654 | { 655 | $validatorHelper = $this->getHelper('validator'); 656 | $serviceHelper = $this->getHelper('service'); 657 | 658 | $validatorHelper->setValidatorClass('IC\Bundle\Base\GeographicalBundle\Validator\Constraints\ValidLocationValidator'); 659 | $validatorHelper->setConstraintClass('IC\Bundle\Base\GeographicalBundle\Validator\Constraints\ValidLocation'); 660 | 661 | // Required mocking 662 | $geolocationService = $serviceHelper->mock('base.geographical.service.geolocation'); 663 | $geolocationService->expects($this->any()) 664 | ->method('convertLocationToCoordinate') 665 | ->will($this->returnValue($this->mockCoordinate())); 666 | 667 | $validatorHelper->getValidator()->setGeolocationService($geolocationService); 668 | 669 | // Testing 670 | $validatorHelper->success($value); 671 | } 672 | ``` 673 | 674 | #### Unit/Entity Helper 675 | 676 | This helper is available to you under the name `Unit/Entity`. 677 | The Unit/Entity helper helps to create Entity stubs where there is no setId() method. 678 | 679 | ```php 680 | public function testFoo() 681 | { 682 | $entityHelper = $this->getHelper('Unit/Entity'); 683 | 684 | $entity = $entityHelper->createMock('IC\Bundle\Base\GeographicalBundle\Entity\Country', 'us'); 685 | 686 | ... 687 | } 688 | ``` 689 | 690 | #### Unit/Function Helper 691 | 692 | This helper is available to you under the name `Unit/Function`. 693 | The Unit/Function helper helps to mock built-in PHP functions. Note: the subject under test must 694 | be a namespaced class. 695 | 696 | ```php 697 | public function testFoo() 698 | { 699 | $functionHelper = $this->getHelper('Unit/Function'); 700 | 701 | // mock ftp_open() to return null (default) 702 | $functionHelper->mock('ftp_open'); 703 | 704 | // mock ftp_open() to return true 705 | $functionHelper->mock('ftp_open', true); 706 | 707 | // mock ftp_open() with callable 708 | $functionHelper->mock('ftp_open', function () { return null; }); 709 | 710 | // mock ftp_open() with a mock object; note: the method is always 'invoke' 711 | $fopenProxy = $functionHelper->createMock(); 712 | $fopenProxy->expects($this->once()) 713 | ->method('invoke') 714 | ->will($this->returnValue(true)); 715 | 716 | $functionHelper->mock('ftp_open', $fopenProxy); 717 | 718 | ... 719 | } 720 | ``` 721 | 722 | ### Creating and registering your own helper 723 | 724 | Registering a new helper is required to override the protected static method 725 | `initializeHelperList`: 726 | 727 | ```php 728 | protected static function initializeHelperList() 729 | { 730 | $helperList = parent::initializeHelperList(); 731 | 732 | $helperList->set('my', 'IC\Bundle\Site\DemoBundle\Test\Helper\MyHelper'); 733 | // Retrieve as: $myHelper = $this->getHelper('my'); 734 | 735 | return $helperList; 736 | } 737 | ``` 738 | -------------------------------------------------------------------------------- /Test/BundleTestCase.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Ryan Albon 13 | */ 14 | abstract class BundleTestCase extends ContainerAwareTestCase 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /Test/ContainerAwareTestCase.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | abstract class ContainerAwareTestCase extends TestCase 16 | { 17 | /** 18 | * @var \Symfony\Component\DependencyInjection\ContainerBuilder 19 | */ 20 | protected $container; 21 | 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | protected function setUp() 26 | { 27 | parent::setUp(); 28 | 29 | $this->container = new ContainerBuilder(); 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | protected function tearDown() 36 | { 37 | unset($this->container); 38 | 39 | parent::tearDown(); 40 | } 41 | 42 | /** 43 | * Retrieve the container 44 | * 45 | * @return \Symfony\Component\DependencyInjection\ContainerBuilder 46 | */ 47 | public function getContainer() 48 | { 49 | return $this->container; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Test/DependencyInjection/ConfigurationTestCase.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | abstract class ConfigurationTestCase extends TestCase 18 | { 19 | /** 20 | * @var \Symfony\Component\Config\Definition\Processor 21 | */ 22 | protected $processor; 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | protected function setUp() 28 | { 29 | parent::setUp(); 30 | 31 | $this->processor = new Processor(); 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | protected function tearDown() 38 | { 39 | unset($this->processor); 40 | 41 | parent::tearDown(); 42 | } 43 | 44 | /** 45 | * Processes an array of raw configuration and returns a compiled version. 46 | * 47 | * @param \Symfony\Component\Config\Definition\ConfigurationInterface $configuration 48 | * @param array $rawConfiguration 49 | * 50 | * @return array A normalized array 51 | */ 52 | public function processConfiguration(ConfigurationInterface $configuration, array $rawConfiguration) 53 | { 54 | return $this->processor->processConfiguration($configuration, $rawConfiguration); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Test/DependencyInjection/ExtensionTestCase.php: -------------------------------------------------------------------------------- 1 | 16 | * @author Ryan Albon 17 | * @author John Cartwright 18 | */ 19 | abstract class ExtensionTestCase extends ContainerAwareTestCase 20 | { 21 | /** 22 | * Loads the configuration into a provided Extension. 23 | * 24 | * @param \Symfony\Component\HttpKernel\DependencyInjection\Extension $extension 25 | * @param array $configuration 26 | */ 27 | protected function load(Extension $extension, array $configuration) 28 | { 29 | $extension->load(array($configuration), $this->container); 30 | } 31 | 32 | /** 33 | * Assertion on the alias of a Container Builder. 34 | * 35 | * @param string $expected 36 | * @param string $key 37 | */ 38 | public function assertAlias($expected, $key) 39 | { 40 | $alias = (string) $this->container->getAlias($key); 41 | 42 | $this->assertEquals($expected, $alias, sprintf('%s alias is correct', $key)); 43 | } 44 | 45 | /** 46 | * Assertion on the parameter of a Container Builder. 47 | * 48 | * @param string $expected 49 | * @param string $key 50 | */ 51 | public function assertParameter($expected, $key) 52 | { 53 | $parameter = $this->container->getParameter($key); 54 | 55 | $this->assertEquals($expected, $parameter, sprintf('%s parameter is correct', $key)); 56 | } 57 | 58 | /** 59 | * Assertion on the Definition existance of a Container Builder. 60 | * 61 | * @param string $id 62 | */ 63 | public function assertHasDefinition($id) 64 | { 65 | $actual = $this->container->hasDefinition($id) ?: $this->container->hasAlias($id); 66 | 67 | $this->assertTrue($actual, sprintf('Expected definition "%s" to exist', $id)); 68 | } 69 | 70 | /** 71 | * Assertion on the Constructor Arguments of a DIC Service Definition. 72 | * 73 | * @param \Symfony\Component\DependencyInjection\Definition $definition 74 | * @param array $argumentList 75 | */ 76 | public function assertDICConstructorArguments(Definition $definition, array $argumentList) 77 | { 78 | $this->assertEquals( 79 | $argumentList, 80 | $definition->getArguments(), 81 | sprintf( 82 | 'Expected and actual DIC Service constructor argumentList of definition "%s" do not match.', 83 | $definition->getClass() 84 | ) 85 | ); 86 | } 87 | 88 | /** 89 | * Assertion on the Class of a DIC Service Definition. 90 | * 91 | * @param \Symfony\Component\DependencyInjection\Definition $definition 92 | * @param string $expectedClass 93 | */ 94 | public function assertDICDefinitionClass(Definition $definition, $expectedClass) 95 | { 96 | $this->assertEquals( 97 | $expectedClass, 98 | $definition->getClass(), 99 | "Expected Class of the DIC Container Service Definition is wrong." 100 | ); 101 | } 102 | 103 | /** 104 | * Assertion on the called Method of a DIC Service Definition. 105 | * 106 | * @param \Symfony\Component\DependencyInjection\Definition $definition 107 | * @param string $methodName 108 | * @param array $parameterList 109 | */ 110 | public function assertDICDefinitionMethodCall(Definition $definition, $methodName, array $parameterList = null) 111 | { 112 | $callList = $definition->getMethodCalls(); 113 | $matchedCall = null; 114 | 115 | foreach ($callList as $call) { 116 | if ($call[0] === $methodName) { 117 | $matchedCall = $call; 118 | 119 | break; 120 | } 121 | } 122 | 123 | if ( ! $matchedCall) { 124 | $this->fail( 125 | sprintf('Method "%s" is was expected to be called.', $methodName) 126 | ); 127 | } 128 | 129 | if ($parameterList !== null) { 130 | $this->assertEquals( 131 | $parameterList, 132 | $matchedCall[1], 133 | sprintf('Expected parameters to method "%s" do not match the actual parameters.', $methodName) 134 | ); 135 | } 136 | } 137 | 138 | /** 139 | * Assertion on the called Method position of a DIC Service Definition. 140 | * 141 | * @param integer $position 142 | * @param \Symfony\Component\DependencyInjection\Definition $definition 143 | * @param string $methodName 144 | * @param array $parameterList 145 | */ 146 | public function assertDICDefinitionMethodCallAt($position, Definition $definition, $methodName, array $parameterList = null) 147 | { 148 | $callList = $definition->getMethodCalls(); 149 | 150 | if ( ! isset($callList[$position][0])) { 151 | // Throws an Exception 152 | $this->fail( 153 | sprintf('Method "%s" is expected to be called at position %s.', $methodName, $position) 154 | ); 155 | } 156 | 157 | $this->assertEquals( 158 | $methodName, 159 | $callList[$position][0], 160 | sprintf('Method "%s" is expected to be called at position %s.', $methodName, $position) 161 | ); 162 | 163 | if ($parameterList !== null) { 164 | $this->assertEquals( 165 | $parameterList, 166 | $callList[$position][1], 167 | sprintf('Expected parameters to methods "%s" do not match the actual parameters.', $methodName) 168 | ); 169 | } 170 | } 171 | 172 | /** 173 | * Assertion on the Definition that a method is not called for a DIC Service Definition. 174 | * 175 | * @param \Symfony\Component\DependencyInjection\Definition $definition 176 | * @param string $methodName 177 | */ 178 | public function assertDICDefinitionMethodNotCalled(Definition $definition, $methodName) 179 | { 180 | $callList = $definition->getMethodCalls(); 181 | 182 | foreach ($callList as $call) { 183 | if ($call[0] === $methodName) { 184 | $this->fail( 185 | sprintf('Method "%s" is not expected to be called.', $methodName) 186 | ); 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /Test/Dummy/FunctionProxy.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class FunctionProxy 14 | { 15 | /** 16 | * Invoke method 17 | */ 18 | public function invoke() 19 | { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Test/Functional/WebTestCase.php: -------------------------------------------------------------------------------- 1 | 17 | * @author Guilherme Blanco 18 | * @author Lukas Kahwe Smith 19 | * @author Danilo Cabello 20 | */ 21 | abstract class WebTestCase extends BaseWebTestCase 22 | { 23 | const ENVIRONMENT = 'test'; 24 | 25 | const MANAGER_NAME = null; 26 | 27 | const FIXTURES_PURGE_MODE = ORMPurger::PURGE_MODE_DELETE; 28 | 29 | /** 30 | * @var boolean 31 | */ 32 | protected $forceSchemaLoad = false; 33 | 34 | /** 35 | * @var \Symfony\Bundle\FrameworkBundle\Client 36 | */ 37 | private $client = null; 38 | 39 | /** 40 | * @var \Doctrine\Common\DataFixtures\ReferenceRepository 41 | */ 42 | private $referenceRepository = null; 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | protected function setUp() 48 | { 49 | parent::setUp(); 50 | 51 | // Initialize the client; it is used in all loaders and helpers 52 | $this->client = static::initializeClient(); 53 | 54 | // Only initialize schema and fixtures if any are defined 55 | $fixtureList = static::getFixtureList(); 56 | 57 | if (empty($fixtureList) && ! $this->forceSchemaLoad) { 58 | return; 59 | } 60 | 61 | $fixtureLoader = new Loader\FixtureLoader($this->client, static::FIXTURES_PURGE_MODE); 62 | $executor = $fixtureLoader->load(static::MANAGER_NAME, $fixtureList); 63 | 64 | $this->referenceRepository = $executor->getReferenceRepository(); 65 | 66 | $cacheDriver = $this->referenceRepository->getManager()->getMetadataFactory()->getCacheDriver(); 67 | 68 | if ($cacheDriver) { 69 | $cacheDriver->deleteAll(); 70 | } 71 | } 72 | 73 | /** 74 | * Unset properties belonging to a class 75 | * 76 | * @param mixed $object 77 | */ 78 | private function unsetPropertyList($object) 79 | { 80 | foreach ($object->getProperties() as $property) { 81 | if ($property->isStatic() || 0 === strncmp($property->getDeclaringClass()->getName(), 'PHPUnit_', 8)) { 82 | continue; 83 | } 84 | 85 | $property->setAccessible(true); 86 | $property->setValue($this, null); 87 | } 88 | } 89 | 90 | /** 91 | * {@inheritdoc} 92 | */ 93 | protected function tearDown() 94 | { 95 | parent::tearDown(); 96 | 97 | for ($reflection = new \ReflectionObject($this); 98 | $reflection !== false; 99 | $reflection = $reflection->getParentClass()) { 100 | $this->unsetPropertyList($reflection); 101 | } 102 | } 103 | 104 | /** 105 | * Retrieve the associated client instance. 106 | * 107 | * @return \Symfony\Bundle\FrameworkBundle\Client 108 | */ 109 | public function getClient() 110 | { 111 | return $this->client; 112 | } 113 | 114 | /** 115 | * Retrieve the associated reference repository. 116 | * 117 | * @return \Doctrine\Common\DataFixtures\ReferenceRepository 118 | */ 119 | public function getReferenceRepository() 120 | { 121 | return $this->referenceRepository; 122 | } 123 | 124 | /** 125 | * Create a mock object of a given class name. 126 | * 127 | * @param string $class Class name 128 | * 129 | * @return mixed 130 | */ 131 | public function createMock($class) 132 | { 133 | return $this 134 | ->getMockBuilder($class) 135 | ->disableOriginalConstructor() 136 | ->getMock(); 137 | } 138 | 139 | /** 140 | * Overwrite assertNull to avoid segmentation fault 141 | * when comparing to Objects. 142 | * 143 | * @param mixed $actual Actual value 144 | * @param string $message Message 145 | */ 146 | public static function assertNull($actual, $message = '') 147 | { 148 | self::assertTrue(is_null($actual), $message); 149 | } 150 | 151 | /** 152 | * Initialize test case client 153 | * 154 | * @return \Symfony\Bundle\FrameworkBundle\Client 155 | */ 156 | protected static function initializeClient() 157 | { 158 | return static::createClient( 159 | static::getClientOptions(), 160 | static::getServerParameters() 161 | ); 162 | } 163 | 164 | /** 165 | * Overwritable method for client's options 166 | * 167 | * @return array 168 | */ 169 | protected static function getClientOptions() 170 | { 171 | return array('environment' => static::ENVIRONMENT); 172 | } 173 | 174 | /** 175 | * Overwritable method for client's server configuration 176 | * 177 | * @return array 178 | */ 179 | protected static function getServerParameters() 180 | { 181 | return array(); 182 | } 183 | 184 | /** 185 | * Overwritable method for fixtures importing 186 | * 187 | * @return array 188 | */ 189 | protected static function getFixtureList() 190 | { 191 | return array(); 192 | } 193 | 194 | /** 195 | * Assert HTTP response status code is 200 OK. 196 | * 197 | * @param \Symfony\Component\HttpFoundation\Response $response 198 | * @param string $message 199 | */ 200 | protected function assertResponseStatusOk($response, $message = '') 201 | { 202 | $this->assertResponseStatusCode(200, $response, $message); 203 | } 204 | 205 | /** 206 | * Assert HTTP response status code is Redirection 3xx. 207 | * 208 | * @param \Symfony\Component\HttpFoundation\Response $response 209 | * @param string $message 210 | */ 211 | protected function assertResponseStatusRedirection($response, $message = '') 212 | { 213 | $this->assertGreaterThanOrEqual(300, $response->getStatusCode(), $message); 214 | $this->assertLessThanOrEqual(399, $response->getStatusCode(), $message); 215 | } 216 | 217 | /** 218 | * Assert HTTP response status code is 301 Moved. 219 | * 220 | * @param \Symfony\Component\HttpFoundation\Response $response 221 | * @param string $message 222 | */ 223 | protected function assertResponseStatusMoved($response, $message = '') 224 | { 225 | $this->assertResponseStatusCode(301, $response, $message); 226 | } 227 | 228 | /** 229 | * Assert HTTP response status code is 302 Found. 230 | * 231 | * @param \Symfony\Component\HttpFoundation\Response $response 232 | * @param string $message 233 | */ 234 | protected function assertResponseStatusFound($response, $message = '') 235 | { 236 | $this->assertResponseStatusCode(302, $response, $message); 237 | } 238 | 239 | /** 240 | * Assert HTTP response status code is 404 Not Found. 241 | * 242 | * @param \Symfony\Component\HttpFoundation\Response $response 243 | * @param string $message 244 | */ 245 | protected function assertResponseStatusNotFound($response, $message = '') 246 | { 247 | $this->assertResponseStatusCode(404, $response, $message); 248 | } 249 | 250 | /** 251 | * Assert HTTP response status code matches the expected. 252 | * 253 | * @param integer $expectedStatusCode 254 | * @param \Symfony\Component\HttpFoundation\Response $response 255 | * @param string $message 256 | */ 257 | protected function assertResponseStatusCode($expectedStatusCode, $response, $message = '') 258 | { 259 | $this->assertEquals($expectedStatusCode, $response->getStatusCode(), $message); 260 | } 261 | 262 | /** 263 | * Assert HTTP response header location matches the expected. 264 | * 265 | * @param string|null $expectedLocation 266 | * @param \Symfony\Component\HttpFoundation\Response $response 267 | * @param string $message 268 | */ 269 | protected function assertResponseRedirectionLocation($expectedLocation, $response, $message = '') 270 | { 271 | $this->assertEquals($expectedLocation, $response->headers->get('location'), $message); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /Test/Helper/AbstractHelper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | abstract class AbstractHelper 16 | { 17 | /** 18 | * @var \IC\Bundle\Base\TestBundle\Test\Functional\WebTestCase 19 | */ 20 | protected $testCase; 21 | 22 | /** 23 | * Define the helper client. 24 | * 25 | * @param \IC\Bundle\Base\TestBundle\Test\Functional\WebTestCase $testCase 26 | */ 27 | public function __construct(WebTestCase $testCase = null) 28 | { 29 | $this->testCase = $testCase; 30 | } 31 | 32 | /** 33 | * Set the web test case 34 | * 35 | * @param \IC\Bundle\Base\TestBundle\Test\Functional\WebTestCase $testCase 36 | */ 37 | public function setTestCase(WebTestCase $testCase) 38 | { 39 | $this->testCase = $testCase; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Test/Helper/CommandHelper.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class CommandHelper extends AbstractHelper 20 | { 21 | /** 22 | * @var \Symfony\Component\Console\Application 23 | */ 24 | private $console; 25 | 26 | /** 27 | * @var string 28 | */ 29 | private $commandName; 30 | 31 | /** 32 | * @var integer 33 | */ 34 | private $maxMemory = 5242880; // 5 * 1024 * 1024 KB 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function __construct(WebTestCase $testCase) 40 | { 41 | parent::__construct($testCase); 42 | 43 | $this->console = new ConsoleApplication($testCase->getClient()->getKernel()); 44 | $this->console->setAutoExit(false); 45 | } 46 | 47 | /** 48 | * Define the command name. 49 | * 50 | * @param string $commandName 51 | */ 52 | public function setCommandName($commandName) 53 | { 54 | $this->commandName = $commandName; 55 | } 56 | 57 | /** 58 | * Retrieve the command input. 59 | * 60 | * @param array $arguments 61 | * 62 | * @return \Symfony\Component\Console\Input\InputInterface 63 | */ 64 | public function getInput(array $arguments = array()) 65 | { 66 | array_unshift($arguments, $this->commandName); 67 | 68 | $input = new ArrayInput($arguments); 69 | $input->setInteractive(false); 70 | 71 | return $input; 72 | } 73 | 74 | /** 75 | * Define the maximum memory for console command output. 76 | * 77 | * @param integer $maxMemory 78 | */ 79 | public function setMaxMemory($maxMemory) 80 | { 81 | $this->maxMemory = $maxMemory; 82 | } 83 | 84 | /** 85 | * Execute a console command. 86 | * 87 | * @param \Symfony\Component\Console\Input\InputInterface|null $input 88 | * 89 | * @return string 90 | */ 91 | public function run(InputInterface $input = null) 92 | { 93 | $handler = fopen('php://temp/maxmemory:' . $this->maxMemory, 'r+'); 94 | $output = new StreamOutput($handler); 95 | 96 | $this->console->run($input, $output); 97 | 98 | rewind($handler); 99 | 100 | return stream_get_contents($handler); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Test/Helper/ControllerHelper.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class ControllerHelper extends AbstractHelper 17 | { 18 | /** 19 | * @var \Symfony\Component\HttpFoundation\Request 20 | */ 21 | private $request; 22 | 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function __construct(WebTestCase $testCase) 27 | { 28 | parent::__construct($testCase); 29 | 30 | $container = $testCase->getClient()->getContainer(); 31 | 32 | $this->request = Request::create('/_internal'); 33 | $this->request->setSession($container->get('session')); 34 | } 35 | 36 | /** 37 | * Retrieve the associated request 38 | * 39 | * @return \Symfony\Component\HttpFoundation\Request 40 | */ 41 | public function getRequest() 42 | { 43 | return $this->request; 44 | } 45 | 46 | /** 47 | * Execute an internal request. 48 | * 49 | * @param string $controller The controller to execute the request 50 | * @param array $options Request Options 51 | * 52 | * @return \Symfony\Component\HttpFoundation\Response 53 | */ 54 | public function render($controller, array $options = array()) 55 | { 56 | $client = $this->testCase->getClient(); 57 | $container = $client->getContainer(); 58 | $httpKernel = $container->get('http_kernel'); 59 | $attributes = isset($options['attributes']) ? $options['attributes'] : array(); 60 | 61 | $container->set('request', $this->request); 62 | 63 | return $httpKernel->forward($controller, $attributes); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Test/Helper/HelperInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface HelperInterface 14 | { 15 | /** 16 | * Constructor. 17 | * 18 | * @param \PHPUnit_Framework_TestCase $testCase 19 | */ 20 | public function __construct(\PHPUnit_Framework_TestCase $testCase); 21 | } 22 | -------------------------------------------------------------------------------- /Test/Helper/PersistenceHelper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class PersistenceHelper extends AbstractHelper 16 | { 17 | /** 18 | * @var \Doctrine\Common\DataFixtures\ReferenceRepository 19 | */ 20 | private $referenceRepository; 21 | 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function __construct(WebTestCase $testCase) 26 | { 27 | parent::__construct($testCase); 28 | 29 | $this->referenceRepository = $testCase->getReferenceRepository(); 30 | } 31 | 32 | /** 33 | * Transforms reference key to a reference or list of references 34 | * 35 | * @param mixed $reference 36 | * 37 | * @return mixed 38 | */ 39 | public function transformToReference($reference) 40 | { 41 | if (is_array($reference)) { 42 | return array_map(array($this->referenceRepository, 'getReference'), $reference); 43 | } 44 | 45 | return $this->referenceRepository->getReference($reference); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Test/Helper/RouteHelper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class RouteHelper extends AbstractHelper 16 | { 17 | /** 18 | * Retrieve a generated route from a route id. 19 | * If the route is not registered then the test is skipped. 20 | * 21 | * @param string $id Route id 22 | * @param array $parameterList Route parameters 23 | * @param boolean $absolute Route absolution 24 | * 25 | * @return mixed 26 | */ 27 | public function getRoute($id, $parameterList = array(), $absolute = false) 28 | { 29 | $client = $this->testCase->getClient(); 30 | $container = $client->getContainer(); 31 | $router = $container->get('router'); 32 | 33 | try { 34 | return $router->generate($id, $parameterList, $absolute); 35 | } catch (RouteNotFoundException $exception) { 36 | $this->testCase->markTestSkipped(sprintf('Failed to acquire route [%s]', $id)); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Test/Helper/ServiceHelper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class ServiceHelper extends AbstractHelper 16 | { 17 | /** 18 | * Retrieve a mock object of a given service name. 19 | * 20 | * @param string $id Service identifier 21 | * @param string $scope Service scope (default="container") 22 | * @param boolean $modifyContainer Modify container? 23 | * 24 | * @return mixed 25 | */ 26 | public function mock($id, $scope = ContainerInterface::SCOPE_CONTAINER, $modifyContainer = true) 27 | { 28 | $client = $this->testCase->getClient(); 29 | $container = $client->getContainer(); 30 | 31 | $service = $container->get($id); 32 | $serviceClass = get_class($service); 33 | $serviceMock = $this->testCase->getClassMock($serviceClass); 34 | 35 | if ($modifyContainer) { 36 | $container->set($id, $serviceMock, $scope); 37 | } 38 | 39 | return $serviceMock; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Test/Helper/SessionHelper.php: -------------------------------------------------------------------------------- 1 | 21 | * @author Lukas Kahwe Smith 22 | * @author John Cartwright 23 | */ 24 | class SessionHelper extends AbstractHelper 25 | { 26 | /** 27 | * @var \Symfony\Component\HttpFoundation\Session\Session 28 | */ 29 | private $session; 30 | 31 | /** 32 | * \Symfony\Component\DependencyInjection\Container 33 | */ 34 | private $container; 35 | 36 | /** 37 | * \Symfony\Bundle\FrameworkBundle\Client 38 | */ 39 | private $client; 40 | 41 | /** 42 | * {@inheritdoc} 43 | * 44 | * @throws \InvalidArgumentException 45 | */ 46 | public function __construct(WebTestCase $testCase) 47 | { 48 | parent::__construct($testCase); 49 | 50 | $this->client = $this->testCase->getClient(); 51 | $this->container = $this->client->getContainer(); 52 | $this->session = $this->container->get('session'); 53 | $cookieJar = $this->client->getCookieJar(); 54 | 55 | // Required parameter to be defined, preventing "hasPreviousSession" in Request to return false. 56 | $options = $this->container->getParameter('session.storage.options'); 57 | 58 | if ( ! $options || ! isset($options['name'])) { 59 | throw new \InvalidArgumentException('Missing session.storage.options#name'); 60 | } 61 | 62 | $this->session->setId(uniqid()); 63 | 64 | // Assign session cookie information referring to session id, allowing consecutive requests session recovering 65 | $cookieJar->set(new Cookie($options['name'], $this->session->getId())); 66 | } 67 | 68 | /** 69 | * Retrieve the associated client session. 70 | * 71 | * @return \Symfony\Component\HttpFoundation\Session\Session 72 | */ 73 | public function getSession() 74 | { 75 | return $this->session; 76 | } 77 | 78 | /** 79 | * Authenticate a given User on a security firewall. 80 | * 81 | * @param \Symfony\Component\Security\Core\User\UserInterface $user User to be authenticated 82 | * @param string $firewall Security firewall name 83 | */ 84 | public function authenticate(UserInterface $user, $firewall) 85 | { 86 | $token = new UsernamePasswordToken($user, null, $firewall, $user->getRoles()); 87 | 88 | $this->dispatchInteractiveLoginEvent($token); 89 | 90 | $this->session->set('_security_' . $firewall, serialize($token)); 91 | $this->session->save(); 92 | 93 | $this->client->getCookieJar()->set(new Cookie($this->session->getName(), $this->session->getId())); 94 | } 95 | 96 | /** 97 | * Dispatch an interactive login event 98 | * 99 | * @param \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken $token 100 | */ 101 | private function dispatchInteractiveLoginEvent(UsernamePasswordToken $token) 102 | { 103 | $request = $this->client->getRequest() ?: new Request(); 104 | $loginEvent = new InteractiveLoginEvent($request, $token); 105 | 106 | $this->container->get('security.context')->setToken($token); 107 | $this->container->get('event_dispatcher')->dispatch(SecurityEvents::INTERACTIVE_LOGIN, $loginEvent); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Test/Helper/Unit/EntityHelper.php: -------------------------------------------------------------------------------- 1 | 12 | * @author John Cartwright 13 | */ 14 | class EntityHelper extends UnitHelper 15 | { 16 | /** 17 | * @var array 18 | */ 19 | private $identityMap = array(); 20 | 21 | /** 22 | * Create an Entity Mock instance. 23 | * 24 | * @param string $entityClassName 25 | * @param mixed $readOnlyPropertyList 26 | * 27 | * @return \PHPUnit_Framework_MockObject_MockObject 28 | */ 29 | public function createMock($entityClassName, $readOnlyPropertyList = null) 30 | { 31 | $readOnlyPropertyList = $this->normalizePropertyList($entityClassName, $readOnlyPropertyList); 32 | $identifier = $this->getIdentifier($readOnlyPropertyList); 33 | 34 | if ($identifier && $identity = $this->getIdentity($entityClassName, $identifier)) { 35 | return $identity; 36 | } 37 | 38 | $entity = $this->createEntityMock($entityClassName, $readOnlyPropertyList); 39 | 40 | if ($identifier) { 41 | $this->storeIdentity($entityClassName, $identifier, $entity); 42 | } 43 | 44 | return $entity; 45 | } 46 | 47 | /** 48 | * Retrieve the identifier. 49 | * 50 | * @param mixed $readOnlyPropertyList 51 | * 52 | * @return mixed string|false 53 | */ 54 | private function getIdentifier($readOnlyPropertyList) 55 | { 56 | if ( ! isset($readOnlyPropertyList['id'])) { 57 | return false; 58 | } 59 | 60 | $value = $readOnlyPropertyList['id']['value']; 61 | 62 | return is_string($value) || is_numeric($value) ? $value : false; 63 | } 64 | 65 | /** 66 | * Normalize the property list. 67 | * 68 | * @param string $entityClassName 69 | * @param mixed $readOnlyPropertyList 70 | * 71 | * @return array 72 | */ 73 | private function normalizePropertyList($entityClassName, $readOnlyPropertyList) 74 | { 75 | // For BC, we need to support allowing this property to come in as an identifier 76 | if (null === $readOnlyPropertyList || ! is_array($readOnlyPropertyList)) { 77 | $readOnlyPropertyList = array('id' => $readOnlyPropertyList); 78 | } 79 | 80 | array_walk($readOnlyPropertyList, function (&$value, $key) { 81 | $value = array( 82 | 'value' => $value, 83 | 'getMethod' => sprintf('get%s', ucfirst($key)), 84 | ); 85 | }); 86 | 87 | return $readOnlyPropertyList; 88 | } 89 | 90 | /** 91 | * Create the entity mock 92 | * 93 | * @param string $entityClassName 94 | * @param array $readOnlyPropertyMethodList 95 | * 96 | * @return object 97 | */ 98 | private function createEntityMock($entityClassName, $readOnlyPropertyMethodList) 99 | { 100 | $methodExtractor = function ($property) { 101 | return $property['getMethod']; 102 | }; 103 | 104 | $entity = $this->testCase 105 | ->getMockBuilder($entityClassName) 106 | ->setMethods(array_map($methodExtractor, $readOnlyPropertyMethodList)) 107 | ->getMock(); 108 | 109 | foreach ($readOnlyPropertyMethodList as $property) { 110 | $entity->expects($this->testCase->any()) 111 | ->method($property['getMethod']) 112 | ->will($this->testCase->returnValue($property['value'])); 113 | } 114 | 115 | return $entity; 116 | } 117 | 118 | /** 119 | * Retrieve the stored reference to the mock object. 120 | * 121 | * @param string $entityClassName 122 | * @param mixed $id 123 | * 124 | * @return mixed 125 | */ 126 | private function getIdentity($entityClassName, $id) 127 | { 128 | if ( ! isset($this->identityMap[$entityClassName]) || ! isset($this->identityMap[$entityClassName][$id])) { 129 | return; 130 | } 131 | 132 | return $this->identityMap[$entityClassName][$id]; 133 | } 134 | 135 | /** 136 | * Store the reference to the mock object. 137 | * 138 | * @param string $entityClassName 139 | * @param mixed $id 140 | * @param object $entity 141 | * 142 | * @return mixed 143 | */ 144 | private function storeIdentity($entityClassName, $id, $entity) 145 | { 146 | $this->identityMap[$entityClassName][$id] = $entity; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Test/Helper/Unit/FunctionHelper.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class FunctionHelper extends UnitHelper 14 | { 15 | public static $functions; 16 | 17 | /** 18 | * Mock a function 19 | * 20 | * @param string $methodName 21 | * @param callable $function 22 | * @param string $namespace 23 | * 24 | * @throws \Exception 25 | * 26 | * @return mixed 27 | */ 28 | public function mock($methodName, $function = null, $namespace = null) 29 | { 30 | // eval() protection 31 | if ( ! preg_match('/^[A-Za-z0-9_]+$/D', $methodName) 32 | || ($namespace && ! preg_match('/^[A-Za-z0-9_]+$/D', $namespace)) 33 | ) { 34 | throw new \Exception('Invalid method name and/or namespace'); 35 | } 36 | 37 | // namespace guesser 38 | if ($namespace === null) { 39 | $caller = $this->getCaller(); 40 | 41 | if ( ! $caller || ! isset($caller[0]) || ($pos = strrpos($caller[0], '\\')) === false) { 42 | throw new \Exception('Unable to mock functions in the root namespace'); 43 | } 44 | 45 | $namespace = str_replace(array('\\Test\\', '\\Tests\\'), '\\', substr($caller[0], 0, $pos)); 46 | } 47 | 48 | if ( ! function_exists('\\' . $namespace . '\\' . $methodName)) { 49 | eval(<<testCase->createMock('IC\Bundle\Base\TestBundle\Test\Dummy\FunctionProxy'); 89 | 90 | return $mock; 91 | } 92 | 93 | /** 94 | * Invoke function 95 | * 96 | * @return mixed 97 | */ 98 | public static function invoke() 99 | { 100 | $args = func_get_args(); 101 | $methodName = array_shift($args); 102 | 103 | $callable = isset(self::$functions[$methodName]) 104 | ? self::$functions[$methodName] 105 | : $methodName; 106 | 107 | return call_user_func_array($callable, $args); 108 | } 109 | 110 | /** 111 | * Has mock? 112 | * 113 | * @param string $methodName 114 | * 115 | * @return boolean 116 | */ 117 | public static function hasMock($methodName) 118 | { 119 | return array_key_exists($methodName, self::$functions); 120 | } 121 | 122 | /** 123 | * Cleanup 124 | */ 125 | public function cleanUp() 126 | { 127 | self::$functions = array(); 128 | } 129 | 130 | /** 131 | * Get caller 132 | * 133 | * @return array|null 134 | */ 135 | private function getCaller() 136 | { 137 | $trace = debug_backtrace(); 138 | 139 | // the first two lines in the call stack are getCaller and mockFunction 140 | if (isset($trace[2])) { 141 | $class = isset($trace[2]['class']) ? $trace[2]['class'] : null; 142 | $function = isset($trace[2]['function']) ? $trace[2]['function'] : null; 143 | 144 | return array($class, $function); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Test/Helper/Unit/UnitHelper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | abstract class UnitHelper implements HelperInterface 16 | { 17 | /** 18 | * @var \IC\Bundle\Base\TestBundle\Test\TestCase 19 | */ 20 | protected $testCase; 21 | 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function __construct(\PHPUnit_Framework_TestCase $testCase) 26 | { 27 | $this->testCase = $testCase; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Test/Helper/ValidatorHelper.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class ValidatorHelper extends AbstractHelper 18 | { 19 | /** 20 | * @var \Symfony\Component\Validator\ExecutionContext 21 | */ 22 | private $context; 23 | 24 | /** 25 | * @var string 26 | */ 27 | private $validatorClass; 28 | 29 | /** 30 | * @var \Symfony\Component\Validator\ConstraintValidator 31 | */ 32 | private $validator; 33 | 34 | /** 35 | * @var string 36 | */ 37 | private $constraintClass; 38 | 39 | /** 40 | * @var \Symfony\Component\Validator\Constraint 41 | */ 42 | private $constraint; 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function __construct(WebTestCase $testCase) 48 | { 49 | parent::__construct($testCase); 50 | 51 | $this->context = $testCase->getClassMock('Symfony\Component\Validator\ExecutionContext'); 52 | } 53 | 54 | /** 55 | * Define the execution context. 56 | * 57 | * @param \Symfony\Component\Validator\ExecutionContext $context 58 | */ 59 | public function setExecutionContext($context) 60 | { 61 | $this->context = $context; 62 | } 63 | 64 | /** 65 | * Define the constraint validator class name. 66 | * 67 | * @param string $validatorClass 68 | */ 69 | public function setValidatorClass($validatorClass) 70 | { 71 | $this->validatorClass = $validatorClass; 72 | } 73 | 74 | /** 75 | * Define the constraint validator. 76 | * 77 | * @param \Symfony\Component\Validator\ConstraintValidator $validator 78 | */ 79 | public function setValidator(ConstraintValidator $validator = null) 80 | { 81 | $this->validator = $validator; 82 | } 83 | 84 | /** 85 | * Retrieve the constraint validator. 86 | * 87 | * @return \Symfony\Component\Validator\ConstraintValidator 88 | */ 89 | public function getValidator() 90 | { 91 | if ( ! $this->validator) { 92 | $this->validator = $this->initializeValidator(); 93 | } 94 | 95 | return $this->validator; 96 | } 97 | 98 | /** 99 | * Define the constraint class name. 100 | * 101 | * @param string $constraintClass 102 | */ 103 | public function setConstraintClass($constraintClass) 104 | { 105 | $this->constraintClass = $constraintClass; 106 | } 107 | 108 | /** 109 | * Define the constraint. 110 | * 111 | * @param \Symfony\Component\Validator\Constraint $constraint 112 | */ 113 | public function setConstraint(Constraint $constraint = null) 114 | { 115 | $this->constraint = $constraint; 116 | } 117 | 118 | /** 119 | * Retrieve the constraint. 120 | * 121 | * @return \Symfony\Component\Validator\Constraint 122 | */ 123 | public function getConstraint() 124 | { 125 | if ( ! $this->constraint) { 126 | $this->constraint = $this->initializeConstraint(); 127 | } 128 | 129 | return $this->constraint; 130 | } 131 | 132 | /** 133 | * Execute the test in success mode. 134 | * 135 | * @param mixed $value 136 | */ 137 | public function success($value) 138 | { 139 | $validator = $this->getValidator(); 140 | $constraint = $this->getConstraint(); 141 | 142 | $this->context 143 | ->expects($this->testCase->never()) 144 | ->method('addViolation'); 145 | 146 | $this->context 147 | ->expects($this->testCase->never()) 148 | ->method('addViolationAtPath'); 149 | 150 | $this->context 151 | ->expects($this->testCase->never()) 152 | ->method('addViolationAtSubPath'); 153 | 154 | $this->context 155 | ->expects($this->testCase->never()) 156 | ->method('addViolationAt'); 157 | 158 | $validator->validate($value, $constraint); 159 | } 160 | 161 | /** 162 | * Execute the test in failure mode at own/current path. 163 | * 164 | * @param mixed $value Value 165 | * @param string $message Message 166 | * @param array $parameters Parameters 167 | */ 168 | public function failure($value, $message, array $parameters = array()) 169 | { 170 | $validator = $this->getValidator(); 171 | $constraint = $this->getConstraint(); 172 | 173 | $this->context 174 | ->expects($this->testCase->once()) 175 | ->method('addViolation') 176 | ->with($message, $parameters); 177 | 178 | $validator->validate($value, $constraint); 179 | } 180 | 181 | /** 182 | * Execute the test in failure mode at full path. 183 | * 184 | * @param mixed $value Value 185 | * @param string $type Type 186 | * @param string $message Message 187 | * @param array $parameters Paramenters 188 | */ 189 | public function failureAtPath($value, $type, $message, array $parameters = array()) 190 | { 191 | $validator = $this->getValidator(); 192 | $constraint = $this->getConstraint(); 193 | 194 | $this->context 195 | ->expects($this->testCase->once()) 196 | ->method('addViolationAtPath') 197 | ->with($type, $message, $parameters); 198 | 199 | $validator->validate($value, $constraint); 200 | } 201 | 202 | /** 203 | * Execute the test in failure mode at sub path. 204 | * 205 | * @param mixed $value Value 206 | * @param string $type Type 207 | * @param string $message Message 208 | * @param array $parameters Parameters 209 | */ 210 | public function failureAtSubPath($value, $type, $message, array $parameters = array()) 211 | { 212 | $validator = $this->getValidator(); 213 | $constraint = $this->getConstraint(); 214 | 215 | $methodName = method_exists('Symfony\Component\Validator\ExecutionContext', 'addViolationAt') 216 | ? 'addViolationAt' 217 | : 'addViolationAtSubPath'; 218 | 219 | $this->context 220 | ->expects($this->testCase->once()) 221 | ->method($methodName) 222 | ->with($type, $message, $parameters); 223 | 224 | $validator->validate($value, $constraint); 225 | } 226 | 227 | /** 228 | * Initialize a constraint validator. 229 | * 230 | * @return \Symfony\Component\Validator\ConstraintValidator 231 | */ 232 | private function initializeValidator() 233 | { 234 | $validatorClass = $this->validatorClass; 235 | $validator = new $validatorClass(); 236 | 237 | $validator->initialize($this->context); 238 | 239 | return $validator; 240 | } 241 | 242 | /** 243 | * Initialize a constraint. 244 | * 245 | * @return \Symfony\Component\Validator\Constraint 246 | */ 247 | private function initializeConstraint() 248 | { 249 | $constraintClass = $this->constraintClass; 250 | $constraint = new $constraintClass(); 251 | 252 | return $constraint; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /Test/Loader/FixtureLoader.php: -------------------------------------------------------------------------------- 1 | 22 | * @author Guilherme Blanco 23 | * @author Lukas Kahwe Smith 24 | * @author John Cartwright 25 | */ 26 | class FixtureLoader 27 | { 28 | /** 29 | * @var \Symfony\Bundle\FrameworkBundle\Client 30 | */ 31 | private $client; 32 | 33 | /** 34 | * @var integer 35 | */ 36 | private $purgeMode; 37 | 38 | /** 39 | * @param \Symfony\Bundle\FrameworkBundle\Client $client 40 | * @param integer $purgeMode 41 | */ 42 | public function __construct(Client $client, $purgeMode = ORMPurger::PURGE_MODE_DELETE) 43 | { 44 | $this->client = $client; 45 | $this->purgeMode = $purgeMode; 46 | } 47 | 48 | /** 49 | * Set the database to the provided fixtures. 50 | * 51 | * Refreshes the database and loads fixtures using the specified classes. 52 | * List of classes is an argument accepting a list of fully qualified class names. 53 | * These classes must implement Doctrine\Common\DataFixtures\FixtureInterface to be loaded 54 | * effectively by DataFixtures Loader::addFixture 55 | * 56 | * When using SQLite driver, this method will work using 2 levels of cache. 57 | * - The first cache level will copy the loaded schema, so it can be restored automatically 58 | * without the overhead of creating the schema for every test case. 59 | * - The second cache level will copy the schema and fixtures loaded, restoring automatically 60 | * in the case you are reusing the same fixtures are loaded again. 61 | * 62 | * Depends on the doctrine data-fixtures library being available in the class path. 63 | * 64 | * @param string $managerName Manager Name 65 | * @param array $classList Class List 66 | * 67 | * @return \Doctrine\Common\DataFixtures\Executor\ORMExecutor 68 | */ 69 | public function load($managerName = null, array $classList = array()) 70 | { 71 | $container = $this->client->getContainer(); 72 | $managerRegistry = $container->get('doctrine'); 73 | $entityManager = $managerRegistry->getManager($managerName); 74 | 75 | // Preparing executor 76 | $executor = $this->prepareExecutor($entityManager); 77 | 78 | // Preparing fixtures 79 | $this->prepareFixtureList($executor, $classList); 80 | 81 | return $executor; 82 | } 83 | 84 | /** 85 | * Prepare executor 86 | * 87 | * @param \Doctrine\ORM\EntityManager $entityManager 88 | * 89 | * @return \Doctrine\Common\DataFixtures\Executor\ORMExecutor 90 | */ 91 | private function prepareExecutor(EntityManager $entityManager) 92 | { 93 | $purger = new ORMPurger($entityManager); 94 | 95 | $purger->setPurgeMode($this->purgeMode); 96 | 97 | $executor = new ORMExecutor($entityManager, $purger); 98 | $repository = new ProxyReferenceRepository($entityManager); 99 | 100 | $executor->setReferenceRepository($repository); 101 | 102 | return $executor; 103 | } 104 | 105 | /** 106 | * Prepare fixtures 107 | * 108 | * @param \Doctrine\Common\DataFixtures\Executor\ORMExecutor $executor Executor 109 | * @param array $classList Class List 110 | */ 111 | private function prepareFixtureList(ORMExecutor $executor, array $classList) 112 | { 113 | $connection = $executor->getObjectManager()->getConnection(); 114 | 115 | sort($classList); 116 | 117 | switch (true) { 118 | case ($connection->getDriver() instanceof SqliteDriver): 119 | $this->loadSqliteFixtureList($executor, $classList); 120 | break; 121 | default: 122 | // Prepare schema 123 | $schemaHelper = new SchemaLoader($executor->getObjectManager()); 124 | $schemaHelper->load($this->purgeMode); 125 | 126 | // Load fixtures 127 | $loader = $this->getLoader($classList); 128 | $fixtureList = $loader->getFixtures(); 129 | 130 | $this->executePreLoadSubscriberEvent($fixtureList, $executor); 131 | 132 | $executor->execute($fixtureList, true); 133 | 134 | $this->executePostLoadSubscriberEvent($fixtureList, $executor); 135 | break; 136 | } 137 | } 138 | 139 | /** 140 | * Executes preload fixture event 141 | * 142 | * @param array $fixtureList List of fixture objects 143 | * @param \Doctrine\Common\DataFixtures\Executor\ORMExecutor $executor Executor 144 | */ 145 | private function executePreLoadSubscriberEvent($fixtureList, ORMExecutor $executor) 146 | { 147 | foreach ($fixtureList as $fixture) { 148 | if ( ! $fixture instanceof PreLoadSubscriberInterface) { 149 | continue; 150 | } 151 | 152 | $fixture->preLoad($executor); 153 | } 154 | } 155 | 156 | /** 157 | * Executes postload fixture event 158 | * 159 | * @param array $fixtureList List of fixture objects 160 | * @param \Doctrine\Common\DataFixtures\Executor\ORMExecutor $executor Executor 161 | */ 162 | private function executePostLoadSubscriberEvent($fixtureList, ORMExecutor $executor) 163 | { 164 | foreach ($fixtureList as $fixture) { 165 | if ( ! $fixture instanceof PostLoadSubscriberInterface) { 166 | continue; 167 | } 168 | 169 | $fixture->postLoad($executor); 170 | } 171 | } 172 | 173 | /** 174 | * Load SQLite data fixture list 175 | * 176 | * @param \Doctrine\Common\DataFixtures\Executor\ORMExecutor $executor Executor 177 | * @param array $classList Class List 178 | */ 179 | private function loadSqliteFixtureList(ORMExecutor $executor, array $classList) 180 | { 181 | $container = $this->client->getContainer(); 182 | $entityManager = $executor->getObjectManager(); 183 | $connection = $entityManager->getConnection(); 184 | $loader = $this->getLoader($classList); 185 | $fixtureList = $loader->getFixtures(); 186 | 187 | $parameters = $connection->getParams(); 188 | $cacheDirectory = $container->getParameter('kernel.cache_dir'); 189 | $database = isset($parameters['path']) ? $parameters['path'] : $parameters['dbname']; 190 | $backupDatabase = sprintf('%s/test_populated_%s.db', $cacheDirectory, md5(serialize($classList))); 191 | 192 | $this->executePreLoadSubscriberEvent($fixtureList, $executor); 193 | 194 | if (file_exists($backupDatabase)) { 195 | $executor->getReferenceRepository()->load($backupDatabase); 196 | 197 | copy($backupDatabase, $database); 198 | 199 | $this->executePostLoadSubscriberEvent($fixtureList, $executor); 200 | 201 | return; 202 | } 203 | 204 | // Prepare schema 205 | $schemaHelper = new SchemaLoader($entityManager); 206 | $schemaHelper->setCacheDirectory($cacheDirectory); 207 | $schemaHelper->load($this->purgeMode); 208 | 209 | // Load fixtures 210 | if ( ! empty($classList)) { 211 | $executor->execute($fixtureList, true); 212 | $executor->getReferenceRepository()->save($backupDatabase); 213 | 214 | copy($database, $backupDatabase); 215 | } 216 | 217 | $this->executePostLoadSubscriberEvent($fixtureList, $executor); 218 | } 219 | 220 | /** 221 | * Retrieve Doctrine DataFixtures loader. 222 | * 223 | * @param array $classList Class List 224 | * 225 | * @return \Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader 226 | */ 227 | private function getLoader(array $classList) 228 | { 229 | $container = $this->client->getContainer(); 230 | $loader = new SymfonyFixtureLoader($container); 231 | 232 | foreach ($classList as $className) { 233 | $this->loadFixtureClass($loader, $className); 234 | } 235 | 236 | return $loader; 237 | } 238 | 239 | /** 240 | * Load a data fixture class. 241 | * 242 | * @param \Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader $loader Loader 243 | * @param string $className Class Name 244 | */ 245 | private function loadFixtureClass(SymfonyFixtureLoader $loader, $className) 246 | { 247 | $fixture = new $className(); 248 | 249 | if ($loader->hasFixture($fixture)) { 250 | return; 251 | } 252 | 253 | $loader->addFixture($fixture); 254 | 255 | if ( ! $fixture instanceof DependentFixtureInterface) { 256 | return; 257 | } 258 | 259 | foreach ($fixture->getDependencies() as $dependency) { 260 | $this->loadFixtureClass($loader, $dependency); 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /Test/Loader/PostLoadSubscriberInterface.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | interface PostLoadSubscriberInterface 16 | { 17 | /** 18 | * Post load fixture event listener 19 | * 20 | * @param \Doctrine\Common\DataFixtures\Executor\ORMExecutor $executor 21 | */ 22 | public function postLoad(ORMExecutor $executor); 23 | } 24 | -------------------------------------------------------------------------------- /Test/Loader/PreLoadSubscriberInterface.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | interface PreLoadSubscriberInterface 16 | { 17 | /** 18 | * Preload fixture event lister 19 | * 20 | * @param \Doctrine\Common\DataFixtures\Executor\ORMExecutor $executor 21 | */ 22 | public function preLoad(ORMExecutor $executor); 23 | } 24 | -------------------------------------------------------------------------------- /Test/Loader/SchemaLoader.php: -------------------------------------------------------------------------------- 1 | 20 | * @author Guilherme Blanco 21 | * @author Lukas Kahwe Smith 22 | */ 23 | class SchemaLoader 24 | { 25 | /** 26 | * @var \Doctrine\ORM\EntityManager 27 | */ 28 | private $entityManager; 29 | 30 | /** 31 | * @var string 32 | */ 33 | private $cacheDirectory; 34 | 35 | /** 36 | * Constructor. 37 | * 38 | * @param \Doctrine\ORM\EntityManager $entityManager 39 | */ 40 | public function __construct(EntityManager $entityManager) 41 | { 42 | $this->entityManager = $entityManager; 43 | } 44 | 45 | /** 46 | * Retrieve the associated EntityManager. 47 | * 48 | * @return \Doctrine\ORM\EntityManager 49 | */ 50 | public function getEntityManager() 51 | { 52 | return $this->entityManager; 53 | } 54 | 55 | /** 56 | * Retrieve the cache directory. 57 | * 58 | * @return string 59 | */ 60 | public function getCacheDirectory() 61 | { 62 | return $this->cacheDirectory; 63 | } 64 | 65 | /** 66 | * Define the cache directory. 67 | * 68 | * @param string $cacheDirectory 69 | */ 70 | public function setCacheDirectory($cacheDirectory) 71 | { 72 | $this->cacheDirectory = $cacheDirectory; 73 | } 74 | 75 | /** 76 | * Load the Database Schema. 77 | * 78 | * @param integer $purgeMode 79 | */ 80 | public function load($purgeMode = ORMPurger::PURGE_MODE_TRUNCATE) 81 | { 82 | $connection = $this->entityManager->getConnection(); 83 | 84 | switch (true) { 85 | case ($connection->getDriver() instanceof SqliteDriver): 86 | $this->loadSqliteSchema(); 87 | break; 88 | default: 89 | $purger = new ORMPurger($this->entityManager); 90 | $purger->setPurgeMode($purgeMode); 91 | 92 | $executor = new ORMExecutor($this->entityManager, $purger); 93 | $executor->setReferenceRepository(new ReferenceRepository($this->entityManager)); 94 | 95 | $executor->purge(); 96 | break; 97 | } 98 | } 99 | 100 | /** 101 | * Load SQLite Driver Schema. 102 | */ 103 | private function loadSqliteSchema() 104 | { 105 | $connection = $this->entityManager->getConnection(); 106 | $metadataList = $this->entityManager->getMetadataFactory()->getAllMetadata(); 107 | 108 | $parameters = $connection->getParams(); 109 | $database = isset($parameters['path']) ? $parameters['path'] : $parameters['dbname']; 110 | $backupDatabase = sprintf('%s/test_%s.db', $this->cacheDirectory, md5(serialize($metadataList))); 111 | 112 | if ( ! (null !== $this->cacheDirectory && file_exists($backupDatabase))) { 113 | $schemaTool = new SchemaTool($this->entityManager); 114 | 115 | $schemaTool->dropDatabase($database); 116 | $schemaTool->createSchema($metadataList); 117 | 118 | // Flip the database saving process. The actual one as the primary 119 | $tmpDatabase = $database; 120 | $database = $backupDatabase; 121 | $backupDatabase = $tmpDatabase; 122 | } 123 | 124 | // Only cache if cache directory is configured 125 | if (null !== $this->cacheDirectory) { 126 | copy($backupDatabase, $database); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Test/Model/JsonResponse.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class JsonResponse 17 | { 18 | /** 19 | * Original response. 20 | * 21 | * @var \Symfony\Component\HttpFoundation\Response 22 | */ 23 | private $response; 24 | 25 | /** 26 | * Decoded JSON response into StdClass object. 27 | * 28 | * @var \StdClass|null 29 | */ 30 | private $responseObject = null; 31 | 32 | /** 33 | * Constructor needs the original response and creates a property accessor. 34 | * 35 | * @param \Symfony\Component\HttpFoundation\Response $response 36 | */ 37 | public function __construct(Response $response) 38 | { 39 | $this->response = $response; 40 | $this->responseObject = json_decode($this->response->getContent()); 41 | $this->accessor = PropertyAccess::getPropertyAccessor(); 42 | } 43 | 44 | /** 45 | * If method is not implemented here, try calling method from original response. 46 | * 47 | * @param string $methodName 48 | * @param array $argumentList 49 | * 50 | * @return mixed Return type will be the same from original method. 51 | */ 52 | public function __call($methodName, $argumentList) 53 | { 54 | return call_user_func_array(array($this->response, $methodName), $argumentList); 55 | } 56 | 57 | /** 58 | * Retrieve decoded JSON response into StdClass object. 59 | * 60 | * @return \StdClass 61 | */ 62 | public function getResponseObject() 63 | { 64 | return $this->responseObject; 65 | } 66 | 67 | /** 68 | * Retrieve property from decoded object. 69 | * 70 | * @param string $propertyPath 71 | * 72 | * @return mixed 73 | */ 74 | public function getProperty($propertyPath) 75 | { 76 | return $this->accessor->getValue($this->getResponseObject(), $propertyPath); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Test/TestCase.php: -------------------------------------------------------------------------------- 1 | 16 | * @author John Cartwright 17 | */ 18 | abstract class TestCase extends BaseTestCase 19 | { 20 | /** 21 | * @var \Doctrine\Common\Collections\ArrayCollection 22 | */ 23 | private $helperList; 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | protected function setUp() 29 | { 30 | parent::setUp(); 31 | 32 | $this->helperList = new ArrayCollection(); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | protected function tearDown() 39 | { 40 | unset($this->helperList); 41 | 42 | parent::tearDown(); 43 | } 44 | 45 | /** 46 | * Retrieve a helper instance giving a helper name. 47 | * 48 | * @param string $name 49 | * 50 | * @throws \InvalidArgumentException 51 | * 52 | * @return \IC\Bundle\Base\TestBundle\Test\Helper\AbstractHelper 53 | */ 54 | public function getHelper($name) 55 | { 56 | if ( ! $this->helperList) { 57 | $this->helperList = new ArrayCollection(); 58 | } 59 | 60 | $normalizedName = $this->helperList->containsKey($name) 61 | ? $name 62 | : Inflector::classify(str_replace('/', '\\', $name)); 63 | 64 | $helperClass = $this->helperList->containsKey($normalizedName) 65 | ? $this->helperList->get($normalizedName) 66 | : sprintf('%s\Helper\%sHelper', __NAMESPACE__, $normalizedName); 67 | 68 | if ( ! is_string($helperClass)) { 69 | return $helperClass; 70 | } 71 | 72 | $reflectionClass = new \ReflectionClass($helperClass); 73 | 74 | if ($reflectionClass->isAbstract() || $reflectionClass->isInterface()) { 75 | throw new \InvalidArgumentException( 76 | sprintf('Cannot create a non-implemented helper "%s".', $helperClass) 77 | ); 78 | } 79 | 80 | $helper = new $helperClass($this); 81 | 82 | $this->helperList->set($normalizedName, $helper); 83 | 84 | return $helper; 85 | } 86 | 87 | /** 88 | * Create a mock object of a given class name. 89 | * 90 | * @param string $class Class name 91 | * @param array $methodList A list of methods to mock 92 | * 93 | * @return mixed 94 | */ 95 | public function createMock($class, $methodList = array()) 96 | { 97 | return $this 98 | ->getMockBuilder($class) 99 | ->setMethods($methodList) 100 | ->disableOriginalConstructor() 101 | ->getMock(); 102 | } 103 | 104 | /** 105 | * Create a mock object of a given abstract class name. 106 | * 107 | * @param string $class Class name 108 | * 109 | * @return mixed 110 | */ 111 | public function createAbstractMock($class) 112 | { 113 | $methodList = $this->getMethodList($class); 114 | 115 | return $this 116 | ->getMockBuilder($class) 117 | ->setMethods($methodList) 118 | ->disableOriginalConstructor() 119 | ->getMockForAbstractClass(); 120 | } 121 | 122 | /** 123 | * Make private and protected function callable 124 | * 125 | * @param mixed $object Subject under test 126 | * @param string $function Function name 127 | * 128 | * @return \ReflectionMethod 129 | */ 130 | public function makeCallable($object, $function) 131 | { 132 | $method = new \ReflectionMethod($object, $function); 133 | $method->setAccessible(true); 134 | 135 | return $method; 136 | } 137 | 138 | /** 139 | * Sets the given property to given value on Object in Test 140 | * 141 | * @param mixed $object Subject under test 142 | * @param string $name Property name 143 | * @param mixed $value Value 144 | */ 145 | public function setPropertyOnObject($object, $name, $value) 146 | { 147 | $property = new \ReflectionProperty($object, $name); 148 | $property->setAccessible(true); 149 | $property->setValue($object, $value); 150 | } 151 | 152 | /** 153 | * Retrieve all the methods from a given class. 154 | * 155 | * @param string $class Class Name 156 | * 157 | * @return array 158 | */ 159 | private function getMethodList($class) 160 | { 161 | $reducer = function (\ReflectionMethod $method) { 162 | return $method->getName(); 163 | }; 164 | 165 | $reflector = new \ReflectionClass($class); 166 | 167 | return array_map($reducer, $reflector->getMethods()); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Test/Validator/ValidatorTestCase.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | abstract class ValidatorTestCase extends TestCase 18 | { 19 | /** 20 | * Assertion of a valid value over a Validator 21 | * 22 | * @param \Symfony\Component\Validator\ConstraintValidator $validator 23 | * @param \Symfony\Component\Validator\Constraint $constraint 24 | * @param mixed $value 25 | */ 26 | public function assertValid(ConstraintValidator $validator, Constraint $constraint, $value) 27 | { 28 | $context = $this->getExecutionContextMock(); 29 | $methodName = method_exists('Symfony\Component\Validator\ExecutionContext', 'addViolationAt') 30 | ? 'addViolationAt' 31 | : 'addViolationAtSubPath'; 32 | 33 | $this->assertContains($constraint->getTargets(), array(Constraint::PROPERTY_CONSTRAINT, Constraint::CLASS_CONSTRAINT)); 34 | $this->assertNotEmpty($constraint->validatedBy()); 35 | 36 | $context 37 | ->expects($this->never()) 38 | ->method('addViolation'); 39 | 40 | $context 41 | ->expects($this->never()) 42 | ->method('addViolationAtPath'); 43 | 44 | $context 45 | ->expects($this->never()) 46 | ->method($methodName); 47 | 48 | $validator->initialize($context); 49 | $validator->validate($value, $constraint); 50 | } 51 | 52 | /** 53 | * Assertion of an error message of an invalid value over a Validator 54 | * 55 | * @param \Symfony\Component\Validator\ConstraintValidator $validator 56 | * @param \Symfony\Component\Validator\Constraint $constraint 57 | * @param mixed $value 58 | * @param string $message 59 | * @param array $parameters 60 | */ 61 | public function assertInvalid(ConstraintValidator $validator, Constraint $constraint, $value, $message, array $parameters = array()) 62 | { 63 | $context = $this->getExecutionContextMock(); 64 | 65 | $context 66 | ->expects($this->once()) 67 | ->method('addViolation') 68 | ->with($message, $parameters); 69 | 70 | $validator->initialize($context); 71 | $validator->validate($value, $constraint); 72 | } 73 | 74 | /** 75 | * Assertion of an error message of an invalid value on a path over a Validator 76 | * 77 | * @param \Symfony\Component\Validator\ConstraintValidator $validator 78 | * @param \Symfony\Component\Validator\Constraint $constraint 79 | * @param mixed $value 80 | * @param string $type 81 | * @param string $message 82 | * @param array $parameters 83 | */ 84 | public function assertInvalidAtPath(ConstraintValidator $validator, Constraint $constraint, $value, $type, $message, array $parameters = array()) 85 | { 86 | $context = $this->getExecutionContextMock(); 87 | 88 | $context 89 | ->expects($this->once()) 90 | ->method('addViolationAtPath') 91 | ->with($type, $message, $parameters); 92 | 93 | $validator->initialize($context); 94 | $validator->validate($value, $constraint); 95 | } 96 | 97 | /** 98 | * Assertion of an error message of an invalid value on a sub-path over a Validator 99 | * 100 | * @param \Symfony\Component\Validator\ConstraintValidator $validator 101 | * @param \Symfony\Component\Validator\Constraint $constraint 102 | * @param mixed $value 103 | * @param string $type 104 | * @param string $message 105 | * @param array $parameters 106 | */ 107 | public function assertInvalidAtSubPath(ConstraintValidator $validator, Constraint $constraint, $value, $type, $message, array $parameters = array()) 108 | { 109 | $context = $this->getExecutionContextMock(); 110 | $methodName = method_exists('Symfony\Component\Validator\ExecutionContext', 'addViolationAt') 111 | ? 'addViolationAt' 112 | : 'addViolationAtSubPath'; 113 | 114 | $context 115 | ->expects($this->once()) 116 | ->method($methodName) 117 | ->with($type, $message, $parameters); 118 | 119 | $validator->initialize($context); 120 | $validator->validate($value, $constraint); 121 | } 122 | 123 | /** 124 | * Retrieve a mocked instance of Validator Execution Context. 125 | * 126 | * @return \Symfony\Component\Validator\ExecutionContext 127 | */ 128 | private function getExecutionContextMock() 129 | { 130 | return $this->getMockBuilder('Symfony\Component\Validator\ExecutionContext') 131 | ->disableOriginalConstructor() 132 | ->getMock(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Test/WebTestCase.php: -------------------------------------------------------------------------------- 1 | 15 | * @deprecated to be removed at a later date. Please use IC\Bundle\Base\TestBundle\Test\Functional\WebTestCase instead. 16 | */ 17 | abstract class WebTestCase extends BaseWebTestCase 18 | { 19 | /** 20 | * @var \Doctrine\Common\Collections\ArrayCollection 21 | */ 22 | private $helperList = null; 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | protected function setUp() 28 | { 29 | parent::setUp(); 30 | 31 | // Initialize the client; it is used in all loaders and helpers 32 | $this->helperList = static::initializeHelperList(); 33 | } 34 | 35 | /** 36 | * Create a mock object of a given class name. 37 | * 38 | * @param string $class Class name 39 | * 40 | * @return mixed 41 | */ 42 | public function getClassMock($class) 43 | { 44 | return $this 45 | ->getMockBuilder($class) 46 | ->disableOriginalConstructor() 47 | ->getMock(); 48 | } 49 | 50 | /** 51 | * Initialize test case helper list 52 | * 53 | * @return \Doctrine\Common\Collections\ArrayCollection 54 | */ 55 | protected static function initializeHelperList() 56 | { 57 | return new ArrayCollection(array( 58 | 'command' => __NAMESPACE__ . '\Helper\CommandHelper', 59 | 'controller' => __NAMESPACE__ . '\Helper\ControllerHelper', 60 | 'service' => __NAMESPACE__ . '\Helper\ServiceHelper', 61 | 'session' => __NAMESPACE__ . '\Helper\SessionHelper', 62 | 'validator' => __NAMESPACE__ . '\Helper\ValidatorHelper', 63 | 'persistence' => __NAMESPACE__ . '\Helper\PersistenceHelper', 64 | 'route' => __NAMESPACE__ . '\Helper\RouteHelper' 65 | )); 66 | } 67 | 68 | /** 69 | * Retrieve a helper instance giving a helper name. 70 | * 71 | * @param string $name 72 | * 73 | * @return \IC\Bundle\Base\TestBundle\Test\Helper\AbstractHelper 74 | */ 75 | public function getHelper($name) 76 | { 77 | $helperClass = $this->helperList->get($name); 78 | 79 | if ($helperClass) { 80 | return new $helperClass($this); 81 | } 82 | 83 | $container = $this->getClient()->getContainer(); 84 | $helperService = $container->get($name); 85 | 86 | if ($helperService) { 87 | $helperService->setTestCase($this); 88 | 89 | return $helperService; 90 | } 91 | } 92 | 93 | /** 94 | * Add helper to helper list. 95 | * 96 | * @param string $name 97 | * @param string $namespace 98 | */ 99 | public function addHelper($name, $namespace) 100 | { 101 | $this->helperList->set($name, $namespace); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "instaclick/base-test-bundle", 3 | "description": "This bundle provides lower level support for functional tests on Symfony2.", 4 | "keywords": ["symfony2"], 5 | "type": "symfony-bundle", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "InstaClick Inc.", 10 | "homepage": "http://www.instaclick.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.3.2", 15 | "symfony/framework-bundle": ">=2.0,<3.0-dev", 16 | "doctrine/doctrine-fixtures-bundle": "2.*" 17 | }, 18 | "autoload": { 19 | "psr-0": { 20 | "IC\\Bundle\\Base\\TestBundle": "" 21 | } 22 | }, 23 | "target-dir" : "IC/Bundle/Base/TestBundle" 24 | } 25 | --------------------------------------------------------------------------------