Completed
Pull Request — master (#245)
by Alexis
02:33
created

Test/WebTestCase.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 58 and the first side effect is on line 41.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
/*
4
 * This file is part of the Liip/FunctionalTestBundle
5
 *
6
 * (c) Lukas Kahwe Smith <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Liip\FunctionalTestBundle\Test;
13
14
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
15
use Symfony\Bundle\FrameworkBundle\Console\Application;
16
use Symfony\Bundle\FrameworkBundle\Client;
17
use Symfony\Component\Console\Input\ArrayInput;
18
use Symfony\Component\Console\Output\OutputInterface;
19
use Symfony\Component\Console\Output\StreamOutput;
20
use Symfony\Component\DomCrawler\Crawler;
21
use Symfony\Component\BrowserKit\Cookie;
22
use Symfony\Component\HttpKernel\Kernel;
23
use Symfony\Component\HttpFoundation\Response;
24
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
25
use Symfony\Component\Security\Core\User\UserInterface;
26
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
27
use Symfony\Component\DependencyInjection\ContainerInterface;
28
use Symfony\Component\HttpFoundation\Session\Session;
29
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
30
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
31
use Doctrine\Common\Persistence\ObjectManager;
32
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
33
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
34
use Liip\FunctionalTestBundle\Utils\FixturesLoader;
35
36
/**
37
 * @author Lea Haensenberger
38
 * @author Lukas Kahwe Smith <[email protected]>
39
 * @author Benjamin Eberlei <[email protected]>
40
 */
41
abstract class WebTestCase extends BaseWebTestCase
0 ignored issues
show
Possible parse error: class missing opening or closing brace
Loading history...
42
{
43
    protected $environment = 'test';
44
    protected $containers;
45
    protected $kernelDir;
46
    // 5 * 1024 * 1024 KB
47
    protected $maxMemory = 5242880;
48
49
    // RUN COMMAND
50
    protected $verbosityLevel;
51
    protected $decorated;
52
53
    /**
54
     * @var array
55
     */
56
    private $firewallLogins = array();
57
58
    protected static function getKernelClass()
59
    {
60
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : self::getPhpUnitXmlDir();
61
62
        list($appname) = explode('\\', get_called_class());
63
64
        $class = $appname.'Kernel';
65
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
66
        if (!file_exists($file)) {
67
            return parent::getKernelClass();
68
        }
69 1
        require_once $file;
70
71 1
        return $class;
72
    }
73 1
74
    /**
75 1
     * Creates a mock object of a service identified by its id.
76 1
     *
77 1
     * @param string $id
78 1
     *
79
     * @return \PHPUnit_Framework_MockObject_MockBuilder
80
     */
81
    protected function getServiceMockBuilder($id)
82
    {
83
        $service = $this->getContainer()->get($id);
84
        $class = get_class($service);
85
86
        return $this->getMockBuilder($class)->disableOriginalConstructor();
87
    }
88
89
    /**
90
     * Builds up the environment to run the given command.
91
     *
92 2
     * @param string $name
93
     * @param array  $params
94
     * @param bool   $reuseKernel
95 2
     *
96
     * @return string
97
     */
98
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
99
    {
100
        array_unshift($params, $name);
101
102
        if (!$reuseKernel) {
103
            if (null !== static::$kernel) {
104
                static::$kernel->shutdown();
105
            }
106
107
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
108
            $kernel->boot();
109 12
        } else {
110
            $kernel = $this->getContainer()->get('kernel');
111 12
        }
112
113 12
        $application = new Application($kernel);
114 12
        $application->setAutoExit(false);
115 9
116 9
        // @codeCoverageIgnoreStart
117
        if ('20301' === Kernel::VERSION_ID) {
118 12
            $params = $this->configureVerbosityForSymfony20301($params);
119 12
        }
120 12
        // @codeCoverageIgnoreEnd
121 2
122
        $input = new ArrayInput($params);
123
        $input->setInteractive(false);
124 12
125 12
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
126
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
127
128
        $application->run($input, $output);
129
130
        rewind($fp);
131
132
        return stream_get_contents($fp);
133 12
    }
134 12
135
    /**
136 12
     * Retrieves the output verbosity level.
137 12
     *
138
     * @see Symfony\Component\Console\Output\OutputInterface for available levels
139 11
     *
140
     * @return int
141 11
     *
142
     * @throws \OutOfBoundsException If the set value isn't accepted
143 11
     */
144
    protected function getVerbosityLevel()
145
    {
146
        // If `null`, is not yet set
147
        if (null === $this->verbosityLevel) {
148
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
149
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
150
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
151
152
            $this->verbosityLevel = constant($verbosity);
153
        }
154
155 12
        // If string, it is set by the developer, so check that the value is an accepted one
156
        if (is_string($this->verbosityLevel)) {
157
            $level = strtoupper($this->verbosityLevel);
158 12
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
159
160 6
            if (!defined($verbosity)) {
161 6
                throw new \OutOfBoundsException(
162
                    sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level)
163 6
                    );
164 6
            }
165
166
            $this->verbosityLevel = constant($verbosity);
167 12
        }
168 6
169 6
        return $this->verbosityLevel;
170
    }
171 6
172 1
    /**
173 1
     * In Symfony 2.3.1 the verbosity level has to be set through {Symfony\Component\Console\Input\ArrayInput} and not
174 1
     * in {Symfony\Component\Console\Output\OutputInterface}.
175
     *
176
     * This method builds $params to be passed to {Symfony\Component\Console\Input\ArrayInput}.
177 5
     *
178 5
     * @codeCoverageIgnore
179
     *
180 11
     * @param array $params
181
     *
182
     * @return array
183
     */
184
    private function configureVerbosityForSymfony20301(array $params)
185
    {
186
        switch ($this->getVerbosityLevel()) {
187
            case OutputInterface::VERBOSITY_QUIET:
188
                $params['-q'] = '-q';
189
                break;
190
191
            case OutputInterface::VERBOSITY_VERBOSE:
192
                $params['-v'] = '';
193
                break;
194
195
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
196
                $params['-vv'] = '';
197
                break;
198
199
            case OutputInterface::VERBOSITY_DEBUG:
200
                $params['-vvv'] = '';
201
                break;
202
        }
203
204
        return $params;
205
    }
206
207
    public function setVerbosityLevel($level)
208
    {
209
        $this->verbosityLevel = $level;
210
    }
211
212
    /**
213
     * Retrieves the flag indicating if the output should be decorated or not.
214
     *
215
     * @return bool
216
     */
217
    protected function getDecorated()
218 6
    {
219
        if (null === $this->decorated) {
220 6
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
221 6
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
222
        }
223
224
        // Check the local decorated flag
225
        if (false === is_bool($this->decorated)) {
226
            throw new \OutOfBoundsException(
227
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
228 11
            );
229
        }
230 11
231
        return $this->decorated;
232 5
    }
233 5
234
    public function isDecorated($decorated)
235
    {
236 11
        $this->decorated = $decorated;
237
    }
238
239
    /**
240
     * Get an instance of the dependency injection container.
241
     * (this creates a kernel *without* parameters).
242 11
     *
243
     * @return ContainerInterface
244
     */
245 6
    protected function getContainer()
246
    {
247 6
        if (!empty($this->kernelDir)) {
248 6
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
249
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
250
        }
251
252
        $cacheKey = $this->kernelDir.'|'.$this->environment;
253
        if (empty($this->containers[$cacheKey])) {
254
            $options = array(
255
                'environment' => $this->environment,
256 37
            );
257
            $kernel = $this->createKernel($options);
258 37
            $kernel->boot();
259
260
            $this->containers[$cacheKey] = $kernel->getContainer();
261
        }
262
263 37
        if (isset($tmpKernelDir)) {
264 37
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
265
        }
266 37
267 37
        return $this->containers[$cacheKey];
268 37
    }
269 37
270
    /**
271 37
     * Set the database to the provided fixtures.
272 37
     *
273
     * Drops the current database and then loads fixtures using the specified
274 37
     * classes. The parameter is a list of fully qualified class names of
275
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
276
     * so that they can be loaded by the DataFixtures Loader::addFixture
277
     *
278 37
     * When using SQLite this method will automatically make a copy of the
279
     * loaded schema and fixtures which will be restored automatically in
280
     * case the same fixture classes are to be loaded again. Caveat: changes
281
     * to references and/or identities may go undetected.
282
     *
283
     * Depends on the doctrine data-fixtures library being available in the
284
     * class path.
285
     *
286
     * @param array  $classNames   List of fully qualified class names of fixtures to load
287
     * @param string $omName       The name of object manager to use
288
     * @param string $registryName The service id of manager registry to use
289
     * @param int    $purgeMode    Sets the ORM purge mode
290
     *
291 2
     * @return null|AbstractExecutor
292
     */
293 2
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
294
    {
295 2
        $loader = new FixturesLoader($this->getContainer());
296 2
297
        return $loader->loadFixtures($classNames, $omName, $registryName, $purgeMode);
298 2
    }
299 2
300 2
    /**
301 2
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
302
     * @param bool   $append
303 2
     * @param null   $omName
304
     * @param string $registryName
305
     *
306
     * @return array
307
     *
308
     * @throws \BadMethodCallException
309
     */
310
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
311
    {
312
        $loader = new FixturesLoader($this->getContainer());
313
314
<<<<<<< HEAD
0 ignored issues
show
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_SL
Loading history...
315
        return $loader->loadFixtureFiles($paths, $append, $omName, $registryName);
316 3
=======
317
        /** @var ContainerInterface $container */
318 3
        $container = $this->getContainer();
319 3
320
        /** @var ManagerRegistry $registry */
321 3
        $registry = $container->get($registryName);
322 2
323 2
        /** @var EntityManager $om */
324
        $om = $registry->getManager($omName);
325
326 3
        if ($append === false) {
327
            $this->cleanDatabase($registry, $om);
328 3
        }
329
330
        $files = $this->locateResources($paths);
331
332
        // Check if the Hautelook AliceBundle is registered and if yes, use it instead of Nelmio Alice
333
        $hautelookLoaderServiceName = 'hautelook_alice.fixtures.loader';
334
        if ($container->has($hautelookLoaderServiceName)) {
335
            $loaderService = $container->get($hautelookLoaderServiceName);
336
            $persisterClass = class_exists('Nelmio\Alice\ORM\Doctrine') ?
337
                'Nelmio\Alice\ORM\Doctrine' :
338
                'Nelmio\Alice\Persister\Doctrine';
339
340
            return $loaderService->load(new $persisterClass($om), $files);
341
        }
342
343
        return Fixtures::load($files, $om);
344
>>>>>>> 2c95f380e7b3cc356e76373c0e4cdbbc4d1a323f
345
    }
346
347
    /**
348
     * Callback function to be executed after Schema creation.
349
     * Use this to execute acl:init or other things necessary.
350
     */
351
    protected function postFixtureSetup()
352
    {
353
    }
354 29
355
    /**
356 29
     * Callback function to be executed after Schema restore.
357
     *
358 29
     * @return WebTestCase
359
     */
360 29
    protected function postFixtureRestore()
361 29
    {
362
    }
363 29
364 29
    /**
365 29
     * Callback function to be executed before Schema restore.
366 29
     *
367 29
     * @param ObjectManager            $manager             The object manager
368
     * @param ProxyReferenceRepository $referenceRepository The reference repository
369 29
     *
370 29
     * @return WebTestCase
371 29
     */
372
    protected function preFixtureRestore(ObjectManager $manager, ProxyReferenceRepository $referenceRepository)
373 29
    {
374 28
    }
375 28
376 24
    /**
377 24
     * Callback function to be executed after save of references.
378
     *
379
     * @param ObjectManager    $manager        The object manager
380
     * @param AbstractExecutor $executor       Executor of the data fixtures
381 24
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
382 24
     *
383
     * @return WebTestCase
384
     */
385
    protected function postReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
386 24
    {
387 6
    }
388
389 6
    /**
390 24
     * Callback function to be executed before save of references.
391
     *
392 24
     * @param ObjectManager    $manager        The object manager
393 5
     * @param AbstractExecutor $executor       Executor of the data fixtures
394 5
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
395 3
     *
396 3
     * @return WebTestCase
397
     */
398 3
    protected function preReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
399
    {
400 3
    }
401
402 3
    /**
403 3
     * Creates an instance of a lightweight Http client.
404 3
     *
405
     * If $authentication is set to 'true' it will use the content of
406 3
     * 'liip_functional_test.authentication' to log in.
407
     *
408 3
     * $params can be used to pass headers to the client, note that they have
409
     * to follow the naming format used in $_SERVER.
410 2
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
411
     *
412
     * @param bool|array $authentication
413 21
     * @param array      $params
414 21
     *
415 21
     * @return Client
416 21
     */
417 21
    protected function makeClient($authentication = false, array $params = array())
418 21
    {
419
        if ($authentication) {
420 21
            if ($authentication === true) {
421 21
                $authentication = array(
422 21
                    'username' => $this->getContainer()
423 25
                        ->getParameter('liip_functional_test.authentication.username'),
424
                    'password' => $this->getContainer()
425 26
                        ->getParameter('liip_functional_test.authentication.password'),
426 5
                );
427 5
            }
428 1
429 1
            $params = array_merge($params, array(
430 1
                'PHP_AUTH_USER' => $authentication['username'],
431 1
                'PHP_AUTH_PW' => $authentication['password'],
432
            ));
433 1
        }
434 1
435 4
        $client = static::createClient(array('environment' => $this->environment), $params);
436 4
437 1
        if ($this->firewallLogins) {
438 1
            // has to be set otherwise "hasPreviousSession" in Request returns false.
439
            $options = $client->getContainer()->getParameter('session.storage.options');
440 4
441
            if (!$options || !isset($options['name'])) {
442
                throw new \InvalidArgumentException('Missing session.storage.options#name');
443 5
            }
444 5
445 5
            $session = $client->getContainer()->get('session');
446
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
447 26
            if ($session instanceof Session) {
448
                $session->setId(uniqid());
449 26
            }
450
451 26
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
452 2
453
            /** @var $user UserInterface */
454 2
            foreach ($this->firewallLogins as $firewallName => $user) {
455 2
                $token = $this->createUserToken($user, $firewallName);
456
457 2
                // BC: security.token_storage is available on Symfony 2.6+
458 2
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
459
                if ($client->getContainer()->has('security.token_storage')) {
460 26
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
461
                } else {
462
                    // This block will never be reached with Symfony 2.6+
463
                    // @codeCoverageIgnoreStart
464
                    $tokenStorage = $client->getContainer()->get('security.context');
465
                    // @codeCoverageIgnoreEnd
466
                }
467
468
                $tokenStorage->setToken($token);
469 5
                $session->set('_security_'.$firewallName, serialize($token));
470
            }
471 5
472
            $session->save();
473 5
        }
474 5
475
        return $client;
476 5
    }
477 1
478 1
    /**
479
     * Create User Token.
480 5
     *
481
     * Factory method for creating a User Token object for the firewall based on
482 5
     * the user object provided. By default it will be a Username/Password
483 1
     * Token based on the user's credentials, but may be overridden for custom
484 1
     * tokens in your applications.
485 5
     *
486
     * @param UserInterface $user         The user object to base the token off of
487
     * @param string        $firewallName name of the firewall provider to use
488
     *
489
     * @return TokenInterface The token to be used in the security context
490
     */
491
    protected function createUserToken(UserInterface $user, $firewallName)
492
    {
493
        return new UsernamePasswordToken(
494 5
            $user,
495
            null,
496 5
            $firewallName,
497
            $user->getRoles()
498 5
        );
499
    }
500 5
501 5
    /**
502 1
     * Extracts the location from the given route.
503 1
     *
504
     * @param string $route    The name of the route
505
     * @param array  $params   Set of parameters
506 4
     * @param int    $absolute
507 5
     *
508
     * @return string
509 5
     */
510
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
511
    {
512
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
513
    }
514
515
    /**
516
     * Checks the success state of a response.
517
     *
518
     * @param Response $response Response object
519
     * @param bool     $success  to define whether the response is expected to be successful
520
     * @param string   $type
521
     */
522 5
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
523
    {
524 5
        $this->getContainer()->get('liip_functional_test.http_assertions')
525
            ->isSuccessful($response, $success, $type);
526
    }
527
528
    /**
529
     * Executes a request on the given url and returns the response contents.
530
     *
531
     * This method also asserts the request was successful.
532 5
     *
533
     * @param string $path           path of the requested page
534
     * @param string $method         The HTTP method to use, defaults to GET
535 5
     * @param bool   $authentication Whether to use authentication, defaults to false
536
     * @param bool   $success        to define whether the response is expected to be successful
537
     *
538 5
     * @return string
539
     */
540 5
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
541 5
    {
542 5
        $client = $this->makeClient($authentication);
543
        $client->request($method, $path);
544 5
545
        $content = $client->getResponse()->getContent();
546
        if (is_bool($success)) {
547 5
            $this->isSuccessful($client->getResponse(), $success);
548 5
        }
549 2
550 2
        return $content;
551 2
    }
552 2
553
    /**
554 2
     * Executes a request on the given url and returns a Crawler object.
555
     *
556
     * This method also asserts the request was successful.
557 3
     *
558
     * @param string $path           path of the requested page
559
     * @param string $method         The HTTP method to use, defaults to GET
560
     * @param bool   $authentication Whether to use authentication, defaults to false
561
     * @param bool   $success        Whether the response is expected to be successful
562
     *
563
     * @return Crawler
564 21
     */
565
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
566 21
    {
567
        $client = $this->makeClient($authentication);
568
        $crawler = $client->request($method, $path);
569
570
        $this->isSuccessful($client->getResponse(), $success);
571
572
        return $crawler;
573 3
    }
574
575 3
    /**
576
     * @param UserInterface $user
577
     * @param string        $firewallName
578
     *
579
     * @return WebTestCase
580
     */
581
    public function loginAs(UserInterface $user, $firewallName)
582
    {
583
        $this->firewallLogins[$firewallName] = $user;
584
585 3
        return $this;
586
    }
587 3
588
    /**
589
     * Asserts that the HTTP response code of the last request performed by
590
     * $client matches the expected code. If not, raises an error with more
591
     * information.
592
     *
593
     * @param $expectedStatusCode
594
     * @param Client $client
595
     */
596
    public function assertStatusCode($expectedStatusCode, Client $client)
597
    {
598 2
        $this->getContainer()->get('liip_functional_test.http_assertions')
599
            ->assertStatusCode($expectedStatusCode, $client);
600 2
    }
601
602
    /**
603
     * Assert that the last validation errors within $container match the
604
     * expected keys.
605
     *
606
     * @param array              $expected  A flat array of field names
607
     * @param ContainerInterface $container
608
     */
609
    public function assertValidationErrors(array $expected, ContainerInterface $container)
610
    {
611 2
        $this->getContainer()->get('liip_functional_test.http_assertions')
612
            ->assertValidationErrors($expected, $container);
613 2
    }
614
}
615