Name: LiipFunctionalTestBundle
Owner: Liip
Description: Some helper classes for writing functional tests in Symfony
Created: 2010-12-25 18:58:03.0
Updated: 2018-01-20 20:06:56.0
Pushed: 2018-01-19 10:22:23.0
Homepage: http://liip.ch
Size: 666
Language: PHP
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
Table of contents:
This Bundle provides base classes for functional tests to assist in setting up test-databases, loading fixtures and HTML5 validation. It also provides a DI aware mock builder for unit tests.
Download the Bundle
Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle:
mposer require --dev liip/functional-test-bundle
This command requires you to have Composer installed globally, as explained in the installation chapter of the Composer documentation.
Note: if you are using PHPUnit 7 or later, you MUST require at least version 2.0 of this bundle; the 1.x versions are incompatible.
Enable the Bundle
Add the following line in the app/AppKernel.php
file to enable this bundle only
for the test
environment:
p
pp/AppKernel.php
..
s AppKernel extends Kernel
public function registerBundles()
{
// ...
if (in_array($this->getEnvironment(), array('dev', 'test'), true)) {
// ...
if ('test' === $this->getEnvironment()) {
$bundles[] = new Liip\FunctionalTestBundle\LiipFunctionalTestBundle();
}
}
return $bundles;
}
// ...
Enable the functionalTest
service adding the following empty configuration:
p/config/config_test.yml
_functional_test: ~
Ensure that the framework is using the filesystem for session storage:
p/config/config_test.yml
ework:
test: ~
session:
storage_id: session.storage.mock_file
Use $this->makeClient
to create a Client object. Client is a Symfony class
that can simulate HTTP requests to your controllers and then inspect the
results. It is covered by the functional tests
section of the Symfony documentation.
After making a request, use assertStatusCode
to verify the HTTP status code.
If it fails it will display the last exception message or validation errors
encountered by the Client object.
If you are expecting validation errors, test them with assertValidationErrors
.
Liip\FunctionalTestBundle\Test\WebTestCase;
s MyControllerTest extends WebTestCase
public function testContact()
{
$client = $this->makeClient();
$crawler = $client->request('GET', '/contact');
$this->assertStatusCode(200, $client);
$form = $crawler->selectButton('Submit')->form();
$crawler = $client->submit($form);
// We should get a validation error for the empty fields.
$this->assertStatusCode(200, $client);
$this->assertValidationErrors(['data.email', 'data.message'], $client->getContainer());
// Try again with with the fields filled out.
$form = $crawler->selectButton('Submit')->form();
$form->setValues(['contact[email]' => 'nobody@example.com', 'contact[message]' => 'Hello']);
$client->submit($form);
$this->assertStatusCode(302, $client);
}
Check that the request succedded:
ent = $this->makeClient();
ent->request('GET', '/contact');
uccessful HTTP request
s->isSuccessful($client->getResponse());
Add false
as the second argument in order to check that the request failed:
ent = $this->makeClient();
ent->request('GET', '/error');
equest returned an error
s->isSuccessful($client->getResponse(), false);
In order to test more specific status codes, use assertStatusCode()
:
Check the HTTP status code from the request:
ent = $this->makeClient();
ent->request('GET', '/contact');
tandard response for successful HTTP request
s->assertStatusCode(302, $client);
Get a Crawler instance from an URL:
wler = $this->fetchCrawler('/contact');
here is one <body> tag
s->assertSame(
1,
$crawler->filter('html > body')->count()
Get the content of an URL:
tent = $this->fetchContent('/contact');
filter()` can't be used since the output is HTML code, check the content directly
s->assertContains(
'<h1>LiipFunctionalTestBundle</h1>',
$content
Generate an URL from a route:
h = $this->getUrl(
'route_name',
array(
'argument_1' => 'liip',
'argument_2' => 'test',
)
ent = $this->makeClient();
ent->request('GET', $path);
s->isSuccessful($client->getResponse());
If you need to test commands, you might need to tweak the output to your needs. You can adjust the command verbosity:
p/config/config_test.yml
_functional_test:
command_verbosity: debug
Supported values are `quiet
, ``
normal`,
verbose``
, `very_verbose
and ``
debug`. The default value is
normal``
.
You can also configure this on a per-test basis:
Liip\FunctionalTestBundle\Test\WebTestCase;
s MyTestCase extends WebTestCase {
public function myTest() {
$this->verbosityLevel = 'debug';
$this->runCommand('myCommand');
}
Depending where your tests are running, you might want to disable the output decorator:
p/config/config_test.yml
_functional_test:
command_decoration: false
The default value is true.
You can also configure this on a per-test basis:
Liip\FunctionalTestBundle\Test\WebTestCase;
s MyTestCase extends WebTestCase {
public function myTest() {
$this->decorated = false;
$this->runCommand('myCommand');
}
If you plan on loading fixtures with your tests, make sure you have the DoctrineFixturesBundle installed and configured first: Doctrine Fixtures setup and configuration instructions
In case tests require database access make sure that the database is created and
proxies are generated. For tests that rely on specific database contents,
write fixture classes and call loadFixtures()
method from the bundled
Test\WebTestCase
class. This will replace the database configured in
config_test.yml
with the specified fixtures. Please note that loadFixtures()
will delete the contents from the database before loading the fixtures. That's
why you should use a designated database for tests.
If you want your tests to run against a completely isolated database (which is recommended for most functional-tests), you can configure your test-environment to use a SQLite-database. This will make your tests run faster and will create a fresh, predictable database for every test you run.
p/config/config_test.yml
rine:
dbal:
default_connection: default
connections:
default:
driver: pdo_sqlite
path: %kernel.cache_dir%/test.db
NB: If you have an existing Doctrine configuration which uses slaves be sure to separate out the configuration for the slaves. Further detail is provided at the bottom of this README.
In order to run your tests even faster, use LiipFunctionalBundle cached database. This will create backups of the initial databases (with all fixtures loaded) and re-load them when required.
Attention: you need Doctrine >= 2.2 to use this feature.
p/config/config_test.yml
_functional_test:
cache_sqlite_db: true
Load your Doctrine fixtures in your tests:
Liip\FunctionalTestBundle\Test\WebTestCase;
s MyControllerTest extends WebTestCase
public function testIndex()
{
// add all your fixtures classes that implement
// Doctrine\Common\DataFixtures\FixtureInterface
$this->loadFixtures(array(
'Bamarni\MainBundle\DataFixtures\ORM\LoadData',
'Me\MyBundle\DataFixtures\ORM\LoadData'
));
// you can now run your functional tests with a populated database
$client = $this->createClient();
// ...
}
If you don't need any fixtures to be loaded and just want to start off with
an empty database (initialized with your schema), you can simply pass an
empty array to loadFixtures
.
Liip\FunctionalTestBundle\Test\WebTestCase;
s MyControllerTest extends WebTestCase
public function testIndex()
{
$this->loadFixtures(array());
// you can now run your functional tests with a populated database
$client = $this->createClient();
// ...
}
Given that you want to exclude some of your doctrine tables from being purged
when loading the fixtures, you can do so by passing an array of tablenames
to the setExcludedDoctrineTables
method before loading the fixtures.
Liip\FunctionalTestBundle\Test\WebTestCase;
s MyControllerTest extends WebTestCase
public function testIndex()
{
$this->setExcludedDoctrineTables(array('my_tablename_not_to_be_purged'));
$this->loadFixtures(array(
'Me\MyBundle\DataFixtures\ORM\LoadData'
));
// ...
}
This bundle uses Doctrine ORM by default. If you are using another driver just specify the service id of the registry manager:
Liip\FunctionalTestBundle\Test\WebTestCase;
s MyControllerTest extends WebTestCase
public function testIndex()
{
$fixtures = array(
'Me\MyBundle\DataFixtures\MongoDB\LoadData'
);
$this->loadFixtures($fixtures, null, 'doctrine_mongodb');
$client = $this->createClient();
}
If you would like to setup your fixtures with yml files using Alice,
Liip\FunctionalTestBundle\Test\WebTestCase
has a helper function loadFixtureFiles
which takes an array of resources, or paths to yml files, and returns an array of objects.
This method uses the Alice Loader
rather than the FunctionalTestBundle's load methods. You should be aware that there are some difference between the ways these two libraries handle loading.
tures = $this->loadFixtureFiles(array(
'@AcmeBundle/DataFixtures/ORM/ObjectData.yml',
'@AcmeBundle/DataFixtures/ORM/AnotherObjectData.yml',
__DIR__.'/../../DataFixtures/ORM/YetAnotherObjectData.yml',
If you want to clear tables you have the following two ways:
The first way is consisted in using the second parameter $append
with value true
. It allows you only to remove all records of table. Values of auto increment won't be reset.
tures = $this->loadFixtureFiles(
array(
'@AcmeBundle/DataFixtures/ORM/ObjectData.yml',
'@AcmeBundle/DataFixtures/ORM/AnotherObjectData.yml',
__DIR__.'/../../DataFixtures/ORM/YetAnotherObjectData.yml',
),
true
The second way is consisted in using the second parameter $append
with value true
and the last parameter $purgeMode
with value Doctrine\Common\DataFixtures\Purger\ORMPurger::PURGE_MODE_TRUNCATE
. It allows you to remove all records of tables with resetting value of auto increment.
p
Doctrine\Common\DataFixtures\Purger\ORMPurger;
es = array(
'@AcmeBundle/DataFixtures/ORM/ObjectData.yml',
'@AcmeBundle/DataFixtures/ORM/AnotherObjectData.yml',
__DIR__.'/../../DataFixtures/ORM/YetAnotherObjectData.yml',
tures = $this->loadFixtureFiles($files, true, null, 'doctrine', ORMPurger::PURGE_MODE_TRUNCATE );
This bundle supports faker providers from HautelookAliceBundle.
Install the bundle with composer require --dev hautelook/alice-bundle:~1.2
and use the
HautelookAliceBundle documentation
in order to define your faker providers.
You'll have to add the following line in the app/AppKernel.php
file:
p
pp/AppKernel.php
..
s AppKernel extends Kernel
public function registerBundles()
{
// ...
if (in_array($this->getEnvironment(), array('dev', 'test'))) {
$bundles[] = new Hautelook\AliceBundle\HautelookAliceBundle();
}
return $bundles;
}
// ...
Then you can load fixtures with $this->loadFixtureFiles(array('@AcmeBundle/?/fixture.yml'));
.
The Bundle will not automatically create your schema for you unless you use SQLite.
If you prefer to use another database but want your schema/fixtures loaded
automatically, you'll need to do that yourself. For example, you could write a
setUp()
function in your test, like so:
Doctrine\ORM\Tools\SchemaTool;
Liip\FunctionalTestBundle\Test\WebTestCase;
s AccountControllerTest extends WebTestCase
public function setUp()
{
$em = $this->getContainer()->get('doctrine')->getManager();
if (!isset($metadatas)) {
$metadatas = $em->getMetadataFactory()->getAllMetadata();
}
$schemaTool = new SchemaTool($em);
$schemaTool->dropDatabase();
if (!empty($metadatas)) {
$schemaTool->createSchema($metadatas);
}
$this->postFixtureSetup();
$fixtures = array(
'Acme\MyBundle\DataFixtures\ORM\LoadUserData',
);
$this->loadFixtures($fixtures);
}
.
Without something like this in place, you'll have to load the schema into your test database manually, for your tests to pass.
In some cases you need to know for example the row ID of an object in order to write a functional test for it, e.g.
$crawler = $client->request('GET', "/profiles/$accountId");
but since the $accountId
keeps changing each test run, you need to figure out its current value. Instead of going via the entity manager repository and querying for the entity, you can use setReference()/getReference()
from the fixture executor directly, as such:
In your fixtures class:
s LoadMemberAccounts extends AbstractFixture
public function load()
{
$account1 = new MemberAccount();
$account1->setName('Alpha');
$this->setReference('account-alpha', $account1);
...
and then in the test case setup:
public function setUp()
{
$this->fixtures = $this->loadFixtures([
'AppBundle\Tests\Fixtures\LoadMemberAccounts'
])->getReferenceRepository();
...
and finally, in the test:
$accountId = $this->fixtures->getReference('account-alpha')->getId();
$crawler = $client->request('GET', "/profiles/$accountId");
The WebTestCase
provides a conveniency method to create an already logged in client using the first parameter of
WebTestCase::makeClient()
.
You have three alternatives to create an already logged in client:
liip_functional_test.authentication
key in the config_test.yml
file;WebTestCase::loginAs()
;config_test.yml
fileYou can set the credentials for your test user in your config_test.yml
file:
_functional_test:
authentication:
username: "a valid username"
password: "the password of that user"
This way using $client = $this->makeClient(true);
your client will be automatically logged in.
You can log in a user directly from your test method by simply passing an array as the first parameter of
WebTestCase::makeClient()
:
dentials = array(
'username' => 'a valid username',
'password' => 'a valid password'
ent = $this->makeClient($credentials);
WebTestCase::loginAs()
To use the method WebTestCase::loginAs()
you have to return the repository containing all references set in the fixtures using the method getReferenceRepository()
and pass the reference of the User
object to the method WebTestCase::loginAs()
.
tures = $this->loadFixtures(array(
'AppBundle\DataFixtures\ORM\LoadUserData'
getReferenceRepository();
s->loginAs($fixtures->getReference('account-alpha'), 'main');
ent = $this->makeClient();
Remember that WebTestCase::loginAs()
accepts objects that implement the interface Symfony\Component\Security\Core\User\UserInterface
.
If you get the error message “Missing session.storage.options#name”, you have to simply add to your
config_test.yml
file the key name
:
ework:
...
session:
# handler_id set to null will use default session handler from php.ini
handler_id: ~
storage_id: session.storage.mock_file
name: MOCKSESSID
As recommended by the Symfony Cookbook in
the chapter about Testing, it is a good idea to to use HTTP Basic Auth for you tests. You can configure the
authentication method in your config_test.yml
:
e best practice in symfony is to put a HTTP basic auth
r the firewall in test env, so that not to have to
ke a request to the login form every single time.
tp://symfony.com/doc/current/cookbook/testing/http_authentication.html
rity:
firewalls:
NAME_OF_YOUR_FIREWALL:
http_basic: ~
For more details, you can check the implementation of WebTestCase
in that bundle.
The online validator: http://validator.nu/ The documentation: http://about.validator.nu/ Documentation about the web service: https://github.com/validator/validator/wiki/Service:-HTTP-interface
To run the validator you require the following dependencies:
Note: The script wants to see a Sun-compatible jar executable. Debian fastjar will not work.
Before starting:
JAVA_HOME
environment variable to the root of the installed JDKjavac
to your PATH
($JAVA_HOME/bin
).--javac=/usr/bin/javac
parameter of the build.py
script.Then:
dir checker; cd checker
t clone https://github.com/validator/validator.git
validator
thon ./build/build.py all; python ./build/build.py all
Note: Yes, the last line is there twice intentionally. Running the script twice tends to fix
a ClassCastException
on the first run.
Note: If at some point for some reason the compilation fails and you are forced to re-run it, it may be necessary to manually remove the htmlparser directory from your disk (the compilation process will complain about that).
This will download the necessary components, compile the validator and run it. This will require about 10 minutes on the first run.
Once the validator is executed it can be reached at http://localhost:8888/ Further instructions on how to build the validator can be found at http://validator.github.io/validator/#build-instructions.
Once the validator has been compiled, it can be run with the following command:
hecker
on build/build.py run
The Liip\FunctionalTestBundle\Test\Html5WebTestCase
class allows to write
functional tests that validate content against the HTML5 validator. In order to
work the validator service must be running on the machine where the tests are
executed.
This class provides the following testing methods:
validateHtml5: This runs a validation on the provided content and returns the full messages of the validation service (including warnings and information). This method is not meant as a test method but rather as a helper to access the validator service. Internally the test method below will use this helper to access the validation service.
assertIsValidHtml5: This will validate the provided content. If the validation succeeds, execution silently continues, otherwise the calling test will fail and display a list of validation errors.
assertIsValidHtml5Snippet: This will validate an HTML5 snippets (i.e. not a full HTML5 document) by wrapping it into an HTML5 document. If the validation succeeds, execution silently continues, otherwise the calling test will fail and display a list of validation errors.
assertIsValidHtml5AjaxResponse: This will validate an AJAX response in a specific format (probably not generic enough). If the validation succeeds, execution silently continues, otherwise the calling test will fail and display a list of validation errors.
setHtml5Wrapper: Allow to change the default HTML5 code that is used as a wrapper around snippets to validate
To catch pages that use way too many database queries, you can enable the query
counter for tests. This will check the profiler for each request made in the
test using the client, and fail the test if the number of queries executed is
larger than the number of queries allowed in the configuration. To enable the
query counter, adjust the config_test.yml
file like this:
ework:
# ...
profiler:
enabled: true
collect: true
_functional_test:
query:
max_query_count: 50
That will limit each request executed within a functional test to 50 queries.
The default value set in the config file should be reasonable to catch pages with high query counts which are obviously mistakes. There will be cases where you know and accept that the request will cause a large number of queries, or where you want to specifically require the page to execute less than x queries, regardless of the amount set in the configuration. For those cases you can set an annotation on the test method that will override the default maximum for any requests made in that test.
To do that, include the Liip\FunctionalTestBundle\Annotations\QueryCount
namespace and add the @QueryCount(100)
annotation, where 100 is the maximum
amount of queries allowed for each request, like this:
Liip\FunctionalTestBundle\Annotations\QueryCount;
s DemoTest extends WebTestCase
/**
* @QueryCount(100)
*/
public function testDoDemoStuff()
{
$client = $this->createClient();
$crawler = $client->request('GET', '/demoPage');
$this->assertTrue($crawler->filter('html:contains("Demo")')->count() > 0);
}
All the functionality of this bundle is primarily for use in the test
environment. The query counter specifically requires services that are only
loaded in the test environment, so the service will only be loaded there. If you
want to use the query counter in a different environment, you'll need to make
sure the bundle is loaded in that environment in your AppKernel.php
file, and
load the test services by adding test
to the framework configuration in the
config.yml (or the configuration file for your environment):
ework:
[...]
test: ~
If that's not what you want to do, and you're getting an exception about this,
check that you're really only loading this bundle in your test
environment
(See step 3 of the installation)
If your main configuration for Doctrine uses Slaves, you need to ensure that the configuration for your SQLite test environment does not include the slave configuration.
The following error can occur in the case where a Doctrine Slave configuration is included:
SQLSTATE[HY000]: General error: 1 no such table NameOfTheTable
This may also manifest itself in the command doctrine:create:schema
doing nothing.
To resolve the issue, it is recommended to configure your Doctrine slaves specifically for the environments that require them.
QueryCount annotations currently only work for tests that have a method name
of testFooBla()
(with a test prefix). The @test
annotation isn't
supported at the moment.
Enabling the Query Counter currently breaks PHPUnit's built-in annotations,
e.g. @dataProvider
, @depends
etc. To fix this, you need to hide the
appropriate PHPUnit annotation from Doctrine's annotation reader using the
@IgnoreAnnotation
annotation:
Liip\FunctionalTestBundle\Test\WebTestCase;
gnoreAnnotation("dataProvider")
gnoreAnnotation("depends")
s DemoTest extends WebTestCase
/ ...