If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeIn practice, the hardest thing about functional testing isn't all the stuff about clicking links or filling out forms. Nope, the toughest thing is taking control of the database!
To do this, there are two big philosophies! First, like with integration tests, we can decide to always start the database empty. And then, if we need data - like we need to add some Enclosures to the database - we will add that data inside the test method itself. This is how I normally code. It's not super fancy, and it means that you need to do extra work in each test to create the exact data you need. But, it also means that each test reads like a complete story. For example, at the top of this test, you would be able to see that we created 3 Enclosure objects. Then, at the bottom, it will make sense why we're expecting to see 3 rows in the table.
The second philosophy, which is a bit simpler, is to load data fixtures. This is what we're going to do: but I'll mention how things would be different if you want to use the first philosophy.
First, install the DoctrineFixturesBundle:
composer require --dev doctrine/doctrine-fixtures-bundle:2.4.1
If you downloaded the course code, in the tutorial/
directory, you should have a DataFixtures
directory. Copy that into your AppBundle
.
... lines 1 - 10 | |
class LoadBasicParkData extends AbstractFixture implements OrderedFixtureInterface | |
{ | |
public function load(ObjectManager $manager) | |
{ | |
$carnivorousEnclosure = new Enclosure(); | |
$manager->persist($carnivorousEnclosure); | |
$this->addReference('carnivorous-enclosure', $carnivorousEnclosure); | |
$herbivorousEnclosure = new Enclosure(); | |
$manager->persist($herbivorousEnclosure); | |
$this->addReference('herbivorous-enclosure', $herbivorousEnclosure); | |
$manager->persist(new Enclosure(true)); | |
$this->addDinosaur($manager, $carnivorousEnclosure, 'Velociraptor', true, 3); | |
$this->addDinosaur($manager, $carnivorousEnclosure, 'Velociraptor', true, 1); | |
$this->addDinosaur($manager, $carnivorousEnclosure, 'Velociraptor', true, 5); | |
$this->addDinosaur($manager, $herbivorousEnclosure, 'Triceratops', false, 7); | |
$manager->flush(); | |
} | |
... lines 33 - 52 | |
} |
... lines 1 - 10 | |
class LoadSecurityData extends AbstractFixture implements OrderedFixtureInterface | |
{ | |
public function load(ObjectManager $manager) | |
{ | |
$herbivorousEnclosure = $this->getReference('herbivorous-enclosure'); | |
$this->addSecurity($herbivorousEnclosure, 'Fence', true); | |
$carnivorousEnclosure = $this->getReference('carnivorous-enclosure'); | |
$this->addSecurity($carnivorousEnclosure, 'Electric fence', false); | |
$this->addSecurity($carnivorousEnclosure, 'Guard tower', false); | |
$manager->flush(); | |
} | |
... lines 26 - 39 | |
} |
These two classes build 3 Enclosures and also add some security to them. But, part of this code is using a setEnclosure()
method on Dinosaur
... and that doesn't exist! Open Dinosaur
, scroll to the bottom, and add it: public function setEnclosure()
with an Enclosure
argument. Set that on the property.
... lines 1 - 10 | |
class Dinosaur | |
{ | |
... lines 13 - 81 | |
public function setEnclosure(Enclosure $enclosure) | |
{ | |
$this->enclosure = $enclosure; | |
} | |
} |
Awesome! Once the bundle finishes downloading open AppKernel
. And inside the if
statement, add new
DoctrineFixturesBundle(). If you're using Flex, this step will have already been done for you automatically.
... lines 1 - 7 | |
class AppKernel extends Kernel | |
{ | |
public function registerBundles() | |
{ | |
... lines 12 - 22 | |
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) { | |
... lines 24 - 26 | |
$bundles[] = new DoctrineFixturesBundle(); | |
... lines 28 - 36 | |
} | |
... lines 38 - 39 | |
} | |
... lines 41 - 60 | |
} |
We haven't hooked the fixtures into our tests yet, but we can at least try them! Run:
php bin/console doctrine:fixtures:load
Go check out the browser! Yes! The fixtures gave us 3 enclosures. That's why I wrote our test to expect 3 rows. If we can load the fixtures when the test runs, we're in business!
Fortunately, LiipFunctionalTestBundle gives us a really nice way to do this. At the top of the test method, add $this->loadFixtures()
and pass an array of the fixture classes you want to load: LoadBasicParkData::class
and LoadSecurityData::class
.
... lines 1 - 8 | |
class DefaultControllerTest extends WebTestCase | |
{ | |
public function testEnclosuresAreShownOnHomepage() | |
{ | |
$this->loadFixtures([ | |
LoadBasicParkData::class, | |
LoadSecurityData::class, | |
]); | |
... lines 17 - 25 | |
} | |
} |
Tip
Since LiipFunctionalTestBundle v3.0 the loadFixtures()
method is no longer supported.
You should use LiipTestFixturesBundle instead
If you're going to use the same set of fixtures for all your test methods, then moving this to setUp()
is a great choice.
Run the tests!
./vendor/bin/phpunit tests/AppBundle/Controller/DefaultControllerTest.php
They work! They pass, over and over again!
So, how would things be different if you did not want to load fixtures? Well, you will still want to empty the database. So, you could use the same trick as the integration tests. Or, you could call $this->loadFixtures()
with an empty array.
Of course, the tests fail. That's because loadFixtures()
empties the database... but then doesn't load anything into it.
Remember, if you choose this philosophy, you're now responsible for creating the data you need. How? The same way you always do: create some Enclosure
objects and then persist them with the EntityManager. And since we're still ultimately extending KernelTestCase
, we already know how to get the EntityManager: with self::$kernel->getContainer()->get('doctrine')->getManager()
.
Actually, it would be great if we had a shortcut like $this->getEntityManager()
for all our test classes. We won't do it in this tutorial, but I highly recommend creating your own base test class with extra shortcut methods. Typically, I'll have one base test class for integration tests - which extends KernelTestCase
- and if necessary, another for my functional tests, which extends WebTestCase
. You can also use traits to share code even better.
The LiipFunctionalTestBundle has two other tricks. First, if you're using SQLite, then it automatically builds the schema for you. Check this out: delete the database file:
rm var/data/test.sqlite
Bye bye database schema! But, when you run the tests, they still pass! When you load the fixtures, it creates the schema too. Thanks friends!
The second trick lives in app/config/config_test.yml
. Add a new option: cache_sqlite_db
set to true
.
... lines 1 - 27 | |
liip_functional_test: | |
cache_sqlite_db: true |
Visually... this doesn't make any difference. BUT! Behind the scenes, cool things are happening. Each time you call loadFixtures()
, it loads the fixtures and then caches the database file. The next time you call loadFixtures()
with the same arguments, it instantly re-uses that cached database file.
Check this out: to simulate loading a lot of fixtures, add a sleep(5)
in one of them. Now, run the test:
./vendor/bin/phpunit tests/AppBundle/Controller/DefaultControllerTest.php
Yea... it's slow. The bundle detected the change we made and was smart enough to know that it needed to reload the fixtures. But the second time... zoom! It's super fast.
The coolest part is that all of this database and fixture-handling stuff from LiipFunctionalTestBundle can be used even if you decide to use a different client - like Mink - instead of Symfony's BrowserKit.
Next, let's look at one more trick you can do with fixtures.
Hey Mr_T
Try to install exact version of fixtures bundle composer require --dev doctrine/doctrine-fixtures-bundle:2.4.1
Cheers!
hey sadikoff
thanks for your reply. I installed the exact version and now I have "doctrine/doctrine-fixtures-bundle": "2.4.1" in composer.json, but I still get the same exact error as before...
If anyone wonders, I've just solved this issue. What I did was to downgrade "phpunit/phpunit" from "^8.5" to "^6.3" and "liip/functional-test-bundle" from "~2.0@alpha" to "^1.8"
hah you did it faster then I get here =) it was tricky... we already tweaked this course install commands to solve most of issues.
Thanks for sharing solution here!
Cheers!
Starting from LiipFunctionTestBundle ^3.0.0 loadFixtures is no longer support.
From de Changelog:
Removed fixtures loading in favor of https://github.com/liip/Lii...
Hey,
When I load fixtures : php bin/console doctrine:fixtures:load, I have an error message :
[InvalidArgumentException]
Class "AppBundle\DataFixtures\ORM\LoadBasicParkData" can't implement "OrderedFixtureInterface" and "DependentFixtureInterface" at the same time.
I use Symfony 3.3.18 and doctrine-fixtures-bundle": "2.4.1"
You know why ?
Hey Stephane
Yep, you can't implement both interfaces :p
If you are on Symfony3 I believe you should only implement "OrderedFixtureInterface"
Cheers!
Hey Diego,
Thank for your reply. But I don't understand why it's not working because I only copy the files about fixtures from tutorial folder. Normaly the code can work with Sf3 ?
Hey Stephane, actually this course is based on Symfony3.3 so it should just work but I just checked the code and we don't implement both interfaces on fixtures classes, somehow you added it. Just stop implementing DependentFixtureInterface
and it should work
Cheers!
MolloKhan LoadBasicParkData
extends Doctrine\Bundle\FixturesBundle\Fixture
which implements DependentFixtureInterface
...
When we remove extended class - addReference
method is no longer available.
When we remove implementation of OrderedFixtureInterface
, well it won't be ordered.
So it's clearly bug in course code.
Probably you should extend Doctrine\Common\DataFixtures\AbstractFixture
.
Hey Kuba,
That was totally a bug, not sure how we missed it when was recording the course. Anyway, it's fixed in the source code and code blocks now. And yes, you're right, we need to extend "Doctrine\Common\DataFixtures\AbstractFixture" instead - good guess! So, it's fixed and I just double checked - fixtures are loaded fine now. Literally, changes were: https://github.com/knpunive...
Thank you for reporting this!
Cheers!
Hey kubaf
Wow, you are totally right. I was on a latest release of DoctrineFixturesBundle where Fixture class doesn't implement DependentFixtureInterface anymore. I think your solution is good enough, just extend from Doctrine\Common\DataFixtures\AbstractFixture
We will see what we can do about tutorial's code
Cheers!
Hi!
As of this moment, Symfony 3.3.10 is not supported any more by the latest version of doctrine/doctrine-fixtures-bundle. The version 3.0.4 does support Symfony 3.3.10. So to get this to work, `composer require --dev doctrine/doctrine-fixtures-bundle:3.0.4` should be the full command.
Hey Bojan Đ.
Thanks for sharing information about bundle compatibility.
BTW to follow course code it will be better to use fixtures-bundle version 2.4.1.
Cheers!
// composer.json
{
"require": {
"php": "^7.0, <7.4",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/doctrine-bundle": "^1.6", // 1.10.3
"doctrine/orm": "^2.5", // v2.7.2
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"sensio/distribution-bundle": "^5.0.19", // v5.0.21
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.28
"symfony/monolog-bundle": "^3.1.0", // v3.1.2
"symfony/polyfill-apcu": "^1.0", // v1.6.0
"symfony/swiftmailer-bundle": "^2.3.10", // v2.6.7
"symfony/symfony": "3.3.*", // v3.3.13
"twig/twig": "^1.0||^2.0" // v2.4.4
},
"require-dev": {
"doctrine/data-fixtures": "^1.3", // 1.3.3
"doctrine/doctrine-fixtures-bundle": "^2.3", // v2.4.1
"liip/functional-test-bundle": "^1.8", // 1.8.0
"phpunit/phpunit": "^6.3", // 6.5.2
"sensio/generator-bundle": "^3.0", // v3.1.6
"symfony/phpunit-bridge": "^3.0" // v3.4.30
}
}
Hi,
when I try run this test "./vendor/bin/phpunit tests/AppBundle/Controller/DefaultControllerTest.php", it fails with the following error: "BadMethodCallException: doctrine/doctrine-fixtures-bundle must be installed to use this method."
In composer.json I have these versions:
"doctrine/doctrine-fixtures-bundle": "2.3",
"liip/functional-test-bundle": "~2.0@alpha",
"liip/test-fixtures-bundle": "^1.0.0",
"phpunit/phpunit": "^8.5",
"doctrine/data-fixtures": "1.3",
"doctrine/doctrine-bundle": "^1.6".
And In AppKernel.php I've registered these bundles for 'dev' and 'test' environments:
$bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
$bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
$bundles[] = new DoctrineFixturesBundle();
if ('dev' === $this->getEnvironment()) {
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
$bundles[] = new Symfony\Bundle\WebServerBundle\WebServerBundle();
}
if ('test' === $this->getEnvironment()) {
$bundles[] = new LiipFunctionalTestBundle();
}
I tried to google for the answer, but didn't find helpful info. Could you help me out to figure out why i get this error, please?