These are some of my notes on writing integration tests for Symfony 3.4 as it was a task which had some gotchas and took me some time to get it working.
In order to access the container of a Symfony project, our PHPUnit tests should extend KernelTestCase
.
As per the documentation How to Test Doctrine Repositories, to retrieve the container, use the following:
/**
* @var \Doctrine\ORM\EntityManager
*/
private $entityManager;
/**
* {@inheritDoc}
*/
protected function setUp()
{
$kernel = self::bootKernel();
$this->entityManager = $kernel->getContainer()
->get('doctrine')
->getManager();
}
Note that since Symfony 3.3 on of the changes introduced is the services _default
where if public: false
is set, we won't be able to access services directly from the container.
That could make it problematic for integration tests since some services may not be accessible.
To get that working, make the value true
in the config_test.yml
file.
e.g.
services:
_defaults:
public: true
Use fixtures to insert known test data to the database before we run the tests.
Using the Doctrine Fixtures Bundle makes it easy to create fixtures.
The main issue I had was that an event listener calling postPersist
was failing trying to access a method in a null project.
The problem in my case was that the Fixtures were being loaded in a different order and hence at that point that particular object didn't exist.
We can fix that by using the OrderedFixtureInterface
e.g.
class MyTableFixtures extends AbstractFixture implements OrderedFixtureInterface
And then return an integer with the order to which this should run by using the getOrder
method.
e.g.
/**
* Get the order of this fixture
*
* @return integer
*/
public function getOrder()
{
return 3;
}
The UserFixtures
is special in that it needs to hash the password.
One way to do it is treating it as a Service as per the documentation.
The other way would be to retrieve it by the container by implementing ContainerAwareInterface
.
We will also need:
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
Clean and populate the database with the fixtures, and run phpunit:
bin/console doctrine:schema:drop --no-interaction --env=test --force
APP_ENV=test bin/console doctrine:schema:create --no-interaction --env=test
APP_ENV=test bin/console doctrine:fixtures:load --no-interaction --env=test
APP_ENV=test vendor/bin/phpunit --group integration
There are many ways to organise your tests and it varies if it is a new or existing project.
In my case it was a project with unit and acceptance tests hence I wanted a simple way to isolate the integration tests.
With PHPUnit annotations we can use the @group name
to group the integration tests. It can either be on the method or the class.
/**
* @group integration
*/
class MyTest extends extends KernelTestCase
Then on the command line to run only the integration tests, run:
APP_ENV=test vendor/bin/phpunit --group integration
And to exclude integration tests from the main suite, run:
vendor/bin/phpunit -c tests/phpunit.xml --testsuite unit --exclude-group integration