/Test/WebTestCase.php

https://github.com/liip/LiipFunctionalTestBundle · PHP · 789 lines · 412 code · 114 blank · 263 comment · 60 complexity · 477973ee9c0ededc4f4828a83b4bd1cf MD5 · raw file

  1. <?php
  2. /*
  3. * This file is part of the Liip/FunctionalTestBundle
  4. *
  5. * (c) Lukas Kahwe Smith <smith@pooteeweet.org>
  6. *
  7. * This source file is subject to the MIT license that is bundled
  8. * with this source code in the file LICENSE.
  9. */
  10. namespace Liip\FunctionalTestBundle\Test;
  11. use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
  12. use Symfony\Bundle\FrameworkBundle\Console\Application;
  13. use Symfony\Bundle\FrameworkBundle\Client;
  14. use Symfony\Component\Console\Input\ArrayInput;
  15. use Symfony\Component\Console\Output\StreamOutput;
  16. use Symfony\Component\DomCrawler\Crawler;
  17. use Symfony\Component\BrowserKit\Cookie;
  18. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  19. use Symfony\Component\Security\Core\User\UserInterface;
  20. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  21. use Symfony\Component\DependencyInjection\ContainerInterface;
  22. use Symfony\Component\HttpFoundation\Session\Session;
  23. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  24. use Symfony\Bridge\Doctrine\ManagerRegistry;
  25. use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
  26. use Doctrine\Common\Persistence\ObjectManager;
  27. use Doctrine\Common\DataFixtures\DependentFixtureInterface;
  28. use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
  29. use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
  30. use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver;
  31. use Doctrine\DBAL\Platforms\MySqlPlatform;
  32. use Doctrine\ORM\Tools\SchemaTool;
  33. use Nelmio\Alice\Fixtures;
  34. /**
  35. * @author Lea Haensenberger
  36. * @author Lukas Kahwe Smith <smith@pooteeweet.org>
  37. * @author Benjamin Eberlei <kontakt@beberlei.de>
  38. */
  39. abstract class WebTestCase extends BaseWebTestCase
  40. {
  41. protected $environment = 'test';
  42. protected $containers;
  43. protected $kernelDir;
  44. // 5 * 1024 * 1024 KB
  45. protected $maxMemory = 5242880;
  46. protected $verbosityLevel;
  47. protected $decorated;
  48. /**
  49. * @var array
  50. */
  51. private $firewallLogins = array();
  52. /**
  53. * @var array
  54. */
  55. private static $cachedMetadatas = array();
  56. protected static function getKernelClass()
  57. {
  58. $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : self::getPhpUnitXmlDir();
  59. list($appname) = explode('\\', get_called_class());
  60. $class = $appname.'Kernel';
  61. $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
  62. if (!file_exists($file)) {
  63. return parent::getKernelClass();
  64. }
  65. require_once $file;
  66. return $class;
  67. }
  68. /**
  69. * Creates a mock object of a service identified by its id.
  70. *
  71. * @param string $id
  72. *
  73. * @return PHPUnit_Framework_MockObject_MockBuilder
  74. */
  75. protected function getServiceMockBuilder($id)
  76. {
  77. $service = $this->getContainer()->get($id);
  78. $class = get_class($service);
  79. return $this->getMockBuilder($class)->disableOriginalConstructor();
  80. }
  81. /**
  82. * Builds up the environment to run the given command.
  83. *
  84. * @param string $name
  85. * @param array $params
  86. * @param bool $reuseKernel
  87. *
  88. * @return string
  89. */
  90. protected function runCommand($name, array $params = array(), $reuseKernel = false)
  91. {
  92. array_unshift($params, $name);
  93. if (!$reuseKernel) {
  94. if (null !== static::$kernel) {
  95. static::$kernel->shutdown();
  96. }
  97. $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
  98. $kernel->boot();
  99. } else {
  100. $kernel = $this->getContainer()->get('kernel');
  101. }
  102. $application = new Application($kernel);
  103. $application->setAutoExit(false);
  104. $input = new ArrayInput($params);
  105. $input->setInteractive(false);
  106. $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
  107. $output = new StreamOutput($fp, $this->retrieveVerbosityLevel(), $this->retrieveDecorated());
  108. $application->run($input, $output);
  109. rewind($fp);
  110. return stream_get_contents($fp);
  111. }
  112. /**
  113. * Retrieves the output verbosity level.
  114. *
  115. * @see Symfony\Component\Console\Output\StreamOutput for available levels
  116. *
  117. * @return string
  118. */
  119. private function retrieveVerbosityLevel()
  120. {
  121. // Returns the local verbosity level
  122. if ($this->verbosityLevel) {
  123. $verbosity = 'StreamOutput::VERBOSITY_'.strtoupper($this->verbosityLevel);
  124. if (defined($verbosity)) {
  125. return $verbosity;
  126. }
  127. }
  128. // Returns the global verbosity level
  129. if ($this->getContainer()->hasParameter('liip_functional_test.command_verbosity')) {
  130. $verbosity = 'StreamOutput::VERBOSITY_'.strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
  131. if (defined($verbosity)) {
  132. return $verbosity;
  133. }
  134. }
  135. // Returns the default verbosity level
  136. return StreamOutput::VERBOSITY_NORMAL;
  137. }
  138. /**
  139. * Retrieves the flag indicating if the output should be decorated or not.
  140. *
  141. * @return bool
  142. */
  143. private function retrieveDecorated()
  144. {
  145. // Returns the local decorated flag
  146. if (is_bool($this->decorated)) {
  147. return $this->decorated;
  148. }
  149. // Returns the global decorated flag
  150. if ($this->getContainer()->hasParameter('liip_functional_test.command_decoration')) {
  151. return $this->getContainer()->getParameter('liip_functional_test.command_decoration');
  152. }
  153. // Returns the default decorated flag
  154. return true;
  155. }
  156. /**
  157. * Get an instance of the dependency injection container.
  158. * (this creates a kernel *without* parameters).
  159. *
  160. * @return ContainerInterface
  161. */
  162. protected function getContainer()
  163. {
  164. if (!empty($this->kernelDir)) {
  165. $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
  166. $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
  167. }
  168. $cacheKey = $this->kernelDir.'|'.$this->environment;
  169. if (empty($this->containers[$cacheKey])) {
  170. $options = array(
  171. 'environment' => $this->environment,
  172. );
  173. $kernel = $this->createKernel($options);
  174. $kernel->boot();
  175. $this->containers[$cacheKey] = $kernel->getContainer();
  176. }
  177. if (isset($tmpKernelDir)) {
  178. $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
  179. }
  180. return $this->containers[$cacheKey];
  181. }
  182. /**
  183. * This function finds the time when the data blocks of a class definition
  184. * file were being written to, that is, the time when the content of the
  185. * file was changed.
  186. *
  187. * @param string $class The fully qualified class name of the fixture class to
  188. * check modification date on.
  189. *
  190. * @return \DateTime|null
  191. */
  192. protected function getFixtureLastModified($class)
  193. {
  194. $lastModifiedDateTime = null;
  195. $reflClass = new \ReflectionClass($class);
  196. $classFileName = $reflClass->getFileName();
  197. if (file_exists($classFileName)) {
  198. $lastModifiedDateTime = new \DateTime();
  199. $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
  200. }
  201. return $lastModifiedDateTime;
  202. }
  203. /**
  204. * Determine if the Fixtures that define a database backup have been
  205. * modified since the backup was made.
  206. *
  207. * @param array $classNames The fixture classnames to check
  208. * @param string $backup The fixture backup SQLite database file path
  209. *
  210. * @return bool TRUE if the backup was made since the modifications to the
  211. * fixtures; FALSE otherwise
  212. */
  213. protected function isBackupUpToDate(array $classNames, $backup)
  214. {
  215. $backupLastModifiedDateTime = new \DateTime();
  216. $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
  217. foreach ($classNames as &$className) {
  218. $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
  219. if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
  220. return false;
  221. }
  222. }
  223. return true;
  224. }
  225. /**
  226. * Set the database to the provided fixtures.
  227. *
  228. * Drops the current database and then loads fixtures using the specified
  229. * classes. The parameter is a list of fully qualified class names of
  230. * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
  231. * so that they can be loaded by the DataFixtures Loader::addFixture
  232. *
  233. * When using SQLite this method will automatically make a copy of the
  234. * loaded schema and fixtures which will be restored automatically in
  235. * case the same fixture classes are to be loaded again. Caveat: changes
  236. * to references and/or identities may go undetected.
  237. *
  238. * Depends on the doctrine data-fixtures library being available in the
  239. * class path.
  240. *
  241. * @param array $classNames List of fully qualified class names of fixtures to load
  242. * @param string $omName The name of object manager to use
  243. * @param string $registryName The service id of manager registry to use
  244. * @param int $purgeMode Sets the ORM purge mode
  245. *
  246. * @return null|AbstractExecutor
  247. */
  248. protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
  249. {
  250. $container = $this->getContainer();
  251. /** @var ManagerRegistry $registry */
  252. $registry = $container->get($registryName);
  253. $om = $registry->getManager($omName);
  254. $type = $registry->getName();
  255. $executorClass = 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
  256. ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
  257. : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
  258. $referenceRepository = new ProxyReferenceRepository($om);
  259. $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
  260. if ($cacheDriver) {
  261. $cacheDriver->deleteAll();
  262. }
  263. if ('ORM' === $type) {
  264. $connection = $om->getConnection();
  265. if ($connection->getDriver() instanceof SqliteDriver) {
  266. $params = $connection->getParams();
  267. if (isset($params['master'])) {
  268. $params = $params['master'];
  269. }
  270. $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
  271. if (!$name) {
  272. throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
  273. }
  274. if (!isset(self::$cachedMetadatas[$omName])) {
  275. self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
  276. usort(self::$cachedMetadatas[$omName], function ($a, $b) { return strcmp($a->name, $b->name); });
  277. }
  278. $metadatas = self::$cachedMetadatas[$omName];
  279. if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
  280. $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
  281. if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
  282. $om->flush();
  283. $om->clear();
  284. $this->preFixtureRestore($om, $referenceRepository);
  285. copy($backup, $name);
  286. $executor = new $executorClass($om);
  287. $executor->setReferenceRepository($referenceRepository);
  288. $executor->getReferenceRepository()->load($backup);
  289. $this->postFixtureRestore();
  290. return $executor;
  291. }
  292. }
  293. // TODO: handle case when using persistent connections. Fail loudly?
  294. $schemaTool = new SchemaTool($om);
  295. $schemaTool->dropDatabase($name);
  296. if (!empty($metadatas)) {
  297. $schemaTool->createSchema($metadatas);
  298. }
  299. $this->postFixtureSetup();
  300. $executor = new $executorClass($om);
  301. $executor->setReferenceRepository($referenceRepository);
  302. }
  303. }
  304. if (empty($executor)) {
  305. $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
  306. if ('PHPCR' === $type) {
  307. $purger = new $purgerClass($om);
  308. $initManager = $container->has('doctrine_phpcr.initializer_manager')
  309. ? $container->get('doctrine_phpcr.initializer_manager')
  310. : null;
  311. $executor = new $executorClass($om, $purger, $initManager);
  312. } else {
  313. $purger = new $purgerClass();
  314. if (null !== $purgeMode) {
  315. $purger->setPurgeMode($purgeMode);
  316. }
  317. $executor = new $executorClass($om, $purger);
  318. }
  319. $executor->setReferenceRepository($referenceRepository);
  320. $executor->purge();
  321. }
  322. $loader = $this->getFixtureLoader($container, $classNames);
  323. $executor->execute($loader->getFixtures(), true);
  324. if (isset($name) && isset($backup)) {
  325. $this->preReferenceSave($om, $executor, $backup);
  326. $executor->getReferenceRepository()->save($backup);
  327. copy($name, $backup);
  328. $this->postReferenceSave($om, $executor, $backup);
  329. }
  330. return $executor;
  331. }
  332. /**
  333. * @param array $paths Either symfony resource locators (@ BundleName/etc) or actual file paths
  334. * @param bool $append
  335. * @param null $omName
  336. * @param string $registryName
  337. *
  338. * @return array
  339. *
  340. * @throws \BadMethodCallException
  341. */
  342. public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
  343. {
  344. if (!class_exists('Nelmio\Alice\Fixtures')) {
  345. throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
  346. }
  347. /** @var ManagerRegistry $registry */
  348. $registry = $this->getContainer()->get($registryName);
  349. $om = $registry->getManager($omName);
  350. if ($append == false) {
  351. //Clean database
  352. $connection = $om->getConnection();
  353. if ($registry->getName() === 'ORM' && $connection->getDatabasePlatform() instanceof MySqlPlatform) {
  354. $connection->query('SET FOREIGN_KEY_CHECKS=0');
  355. }
  356. $this->loadFixtures(array());
  357. if ($registry->getName() === 'ORM' && $connection->getDatabasePlatform() instanceof MySqlPlatform) {
  358. $connection->query('SET FOREIGN_KEY_CHECKS=1');
  359. }
  360. }
  361. $files = array();
  362. $kernel = $this->getContainer()->get('kernel');
  363. foreach ($paths as $path) {
  364. if ($path[0] !== '@' && file_exists($path) === true) {
  365. $files[] = $path;
  366. continue;
  367. }
  368. $files[] = $kernel->locateResource($path);
  369. }
  370. return Fixtures::load($files, $om);
  371. }
  372. /**
  373. * Callback function to be executed after Schema creation.
  374. * Use this to execute acl:init or other things necessary.
  375. */
  376. protected function postFixtureSetup()
  377. {
  378. }
  379. /**
  380. * Callback function to be executed after Schema restore.
  381. *
  382. * @return WebTestCase
  383. */
  384. protected function postFixtureRestore()
  385. {
  386. }
  387. /**
  388. * Callback function to be executed before Schema restore.
  389. *
  390. * @param ObjectManager $manager The object manager
  391. * @param ProxyReferenceRepository $referenceRepository The reference repository
  392. *
  393. * @return WebTestCase
  394. */
  395. protected function preFixtureRestore(ObjectManager $manager, ProxyReferenceRepository $referenceRepository)
  396. {
  397. }
  398. /**
  399. * Callback function to be executed after save of references.
  400. *
  401. * @param ObjectManager $manager The object manager
  402. * @param AbstractExecutor $executor Executor of the data fixtures
  403. * @param string $backupFilePath Path of file used to backup the references of the data fixtures
  404. *
  405. * @return WebTestCase
  406. */
  407. protected function postReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
  408. {
  409. }
  410. /**
  411. * Callback function to be executed before save of references.
  412. *
  413. * @param ObjectManager $manager The object manager
  414. * @param AbstractExecutor $executor Executor of the data fixtures
  415. * @param string $backupFilePath Path of file used to backup the references of the data fixtures
  416. *
  417. * @return WebTestCase
  418. */
  419. protected function preReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
  420. {
  421. }
  422. /**
  423. * Retrieve Doctrine DataFixtures loader.
  424. *
  425. * @param ContainerInterface $container
  426. * @param array $classNames
  427. *
  428. * @return Loader
  429. */
  430. protected function getFixtureLoader(ContainerInterface $container, array $classNames)
  431. {
  432. $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
  433. ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
  434. : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
  435. ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
  436. : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
  437. $loader = new $loaderClass($container);
  438. foreach ($classNames as $className) {
  439. $this->loadFixtureClass($loader, $className);
  440. }
  441. return $loader;
  442. }
  443. /**
  444. * Load a data fixture class.
  445. *
  446. * @param Loader $loader
  447. * @param string $className
  448. */
  449. protected function loadFixtureClass($loader, $className)
  450. {
  451. $fixture = new $className();
  452. if ($loader->hasFixture($fixture)) {
  453. unset($fixture);
  454. return;
  455. }
  456. $loader->addFixture($fixture);
  457. if ($fixture instanceof DependentFixtureInterface) {
  458. foreach ($fixture->getDependencies() as $dependency) {
  459. $this->loadFixtureClass($loader, $dependency);
  460. }
  461. }
  462. }
  463. /**
  464. * Creates an instance of a lightweight Http client.
  465. *
  466. * If $authentication is set to 'true' it will use the content of
  467. * 'liip_functional_test.authentication' to log in.
  468. *
  469. * $params can be used to pass headers to the client, note that they have
  470. * to follow the naming format used in $_SERVER.
  471. * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
  472. *
  473. * @param bool|array $authentication
  474. * @param array $params
  475. *
  476. * @return Client
  477. */
  478. protected function makeClient($authentication = false, array $params = array())
  479. {
  480. if ($authentication) {
  481. if ($authentication === true) {
  482. $authentication = $this->getContainer()->getParameter('liip_functional_test.authentication');
  483. }
  484. $params = array_merge($params, array(
  485. 'PHP_AUTH_USER' => $authentication['username'],
  486. 'PHP_AUTH_PW' => $authentication['password'],
  487. ));
  488. }
  489. $client = static::createClient(array('environment' => $this->environment), $params);
  490. if ($this->firewallLogins) {
  491. // has to be set otherwise "hasPreviousSession" in Request returns false.
  492. $options = $client->getContainer()->getParameter('session.storage.options');
  493. if (!$options || !isset($options['name'])) {
  494. throw new \InvalidArgumentException('Missing session.storage.options#name');
  495. }
  496. $session = $client->getContainer()->get('session');
  497. // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
  498. if ($session instanceof Session) {
  499. $session->setId(uniqid());
  500. }
  501. $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
  502. /** @var $user UserInterface */
  503. foreach ($this->firewallLogins as $firewallName => $user) {
  504. $token = $this->createUserToken($user, $firewallName);
  505. // BC: security.token_storage is available on Symfony 2.6+
  506. // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
  507. if ($client->getContainer()->has('security.token_storage')) {
  508. $tokenStorage = $client->getContainer()->get('security.token_storage');
  509. } else {
  510. // This block will never be reached with Symfony 2.5+
  511. // @codeCoverageIgnoreStart
  512. $tokenStorage = $client->getContainer()->get('security.context');
  513. // @codeCoverageIgnoreEnd
  514. }
  515. $tokenStorage->setToken($token);
  516. $session->set('_security_'.$firewallName, serialize($token));
  517. }
  518. $session->save();
  519. }
  520. return $client;
  521. }
  522. /**
  523. * Create User Token.
  524. *
  525. * Factory method for creating a User Token object for the firewall based on
  526. * the user object provided. By default it will be a Username/Password
  527. * Token based on the user's credentials, but may be overridden for custom
  528. * tokens in your applications.
  529. *
  530. * @param UserInterface $user The user object to base the token off of
  531. * @param string $firewallName name of the firewall provider to use
  532. *
  533. * @return TokenInterface The token to be used in the security context
  534. */
  535. protected function createUserToken(UserInterface $user, $firewallName)
  536. {
  537. return new UsernamePasswordToken(
  538. $user,
  539. null,
  540. $firewallName,
  541. $user->getRoles()
  542. );
  543. }
  544. /**
  545. * Extracts the location from the given route.
  546. *
  547. * @param string $route The name of the route
  548. * @param array $params Set of parameters
  549. * @param bool $absolute
  550. *
  551. * @return string
  552. */
  553. protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
  554. {
  555. return $this->getContainer()->get('router')->generate($route, $params, $absolute);
  556. }
  557. /**
  558. * Checks the success state of a response.
  559. *
  560. * @param Response $response Response object
  561. * @param bool $success to define whether the response is expected to be successful
  562. * @param string $type
  563. */
  564. public function isSuccessful($response, $success = true, $type = 'text/html')
  565. {
  566. try {
  567. $crawler = new Crawler();
  568. $crawler->addContent($response->getContent(), $type);
  569. if (!count($crawler->filter('title'))) {
  570. $title = '['.$response->getStatusCode().'] - '.$response->getContent();
  571. } else {
  572. $title = $crawler->filter('title')->text();
  573. }
  574. } catch (\Exception $e) {
  575. $title = $e->getMessage();
  576. }
  577. if ($success) {
  578. $this->assertTrue($response->isSuccessful(), 'The Response was not successful: '.$title);
  579. } else {
  580. $this->assertFalse($response->isSuccessful(), 'The Response was successful: '.$title);
  581. }
  582. }
  583. /**
  584. * Executes a request on the given url and returns the response contents.
  585. *
  586. * This method also asserts the request was successful.
  587. *
  588. * @param string $path path of the requested page
  589. * @param string $method The HTTP method to use, defaults to GET
  590. * @param bool $authentication Whether to use authentication, defaults to false
  591. * @param bool $success to define whether the response is expected to be successful
  592. *
  593. * @return string
  594. */
  595. public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
  596. {
  597. $client = $this->makeClient($authentication);
  598. $client->request($method, $path);
  599. $content = $client->getResponse()->getContent();
  600. if (is_bool($success)) {
  601. $this->isSuccessful($client->getResponse(), $success);
  602. }
  603. return $content;
  604. }
  605. /**
  606. * Executes a request on the given url and returns a Crawler object.
  607. *
  608. * This method also asserts the request was successful.
  609. *
  610. * @param string $path path of the requested page
  611. * @param string $method The HTTP method to use, defaults to GET
  612. * @param bool $authentication Whether to use authentication, defaults to false
  613. * @param bool $success Whether the response is expected to be successful
  614. *
  615. * @return Crawler
  616. */
  617. public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
  618. {
  619. $client = $this->makeClient($authentication);
  620. $crawler = $client->request($method, $path);
  621. $this->isSuccessful($client->getResponse(), $success);
  622. return $crawler;
  623. }
  624. /**
  625. * @param UserInterface $user
  626. *
  627. * @return WebTestCase
  628. */
  629. public function loginAs(UserInterface $user, $firewallName)
  630. {
  631. $this->firewallLogins[$firewallName] = $user;
  632. return $this;
  633. }
  634. /**
  635. * Asserts that the HTTP response code of the last request performed by
  636. * $client matches the expected code. If not, raises an error with more
  637. * information.
  638. *
  639. * @param $expectedStatusCode
  640. * @param Client $client
  641. */
  642. public function assertStatusCode($expectedStatusCode, Client $client)
  643. {
  644. $helpfulErrorMessage = null;
  645. if ($expectedStatusCode !== $client->getResponse()->getStatusCode()) {
  646. // Get a more useful error message, if available
  647. if ($exception = $client->getContainer()->get('liip_functional_test.exception_listener')->getLastException()) {
  648. $helpfulErrorMessage = $exception->getMessage();
  649. } elseif (count($validationErrors = $client->getContainer()->get('liip_functional_test.validator')->getLastErrors())) {
  650. $helpfulErrorMessage = "Unexpected validation errors:\n";
  651. foreach ($validationErrors as $error) {
  652. $helpfulErrorMessage .= sprintf("+ %s: %s\n", $error->getPropertyPath(), $error->getMessage());
  653. }
  654. } else {
  655. $helpfulErrorMessage = substr($client->getResponse(), 0, 200);
  656. }
  657. }
  658. self::assertEquals($expectedStatusCode, $client->getResponse()->getStatusCode(), $helpfulErrorMessage);
  659. }
  660. /**
  661. * Assert that the last validation errors within $container match the
  662. * expected keys.
  663. *
  664. * @param array $expected A flat array of field names
  665. * @param ContainerInterface $container
  666. */
  667. public function assertValidationErrors(array $expected, ContainerInterface $container)
  668. {
  669. self::assertThat(
  670. $container->get('liip_functional_test.validator')->getLastErrors(),
  671. new ValidationErrorsConstraint($expected),
  672. 'Validation errors should match.'
  673. );
  674. }
  675. }