Phpunit reusable test methods in symfony - symfony

Let's assume that I have three entites: UserEntity, ZooEntity and AnimalEntity. In order to create zoo I have to create user, because zoo has to have owner. In order to create animal I have to create zoo.
Going further I have 3 controllers for each entity. I also have 3 test classes for controller testing.
UserControllerTest
ZooControllerTest
AnimalControllerTest
In animal test, every time, in each test (to make every test independent) I have to create user and then zoo. Therefore I created traits eg: UserTestTrait and ZooTestTrait which have createUser and createZoo(user) methods.
I was wondering about chaning those traits into services. But then where I should keep them?
Would be tests/services/ZooService a good place?
For now I such structure:
tests/Controller/Rest/ZooControllerTest
tests/Controller/Rest/traits/ZooTestTrait
Assuming that I have those services and every service should have access to eg. entity manager, how can I access that in service? eg. ZooService located in tests/services/ZooService
How can I use that service in controller? Lets assume that I would like to have it in setUp method:
protected function setUp(): void {
$kernel = self::bootKernel();
// access the zoo service, that has access to the entity manager
}

I found myself a better approach. Using DoctrineFixturesBundle is a very satisfying way of providing test data. It's even better, when you integrate the Fixtures into your Tests - which might slow them down, but the quality gain is super convenient.
see this tutorial
My AbstractControllerTest Class looks similar to this:
<?php
namespace App\Tests\Functional\Controller;
use App\DataFixtures\AbstractAppFixtures;
use App\DataFixtures\UserFixtures;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\Cookie;
abstract class AbstractControllerTest extends WebTestCase
{
/**
* #var ORMExecutor
*/
private $fixtureExecutor;
/**
* #var ContainerAwareLoader
*/
private $fixtureLoader;
/**
* set up before test
*/
public function setUp(): void {
$kernel = static::getKernelClass();
static::$kernel = new $kernel('dev', true);
static::$kernel->boot();
static::$container = static::$kernel->getContainer();
$this->addFixture(new UserFixtures());
}
/**
* #param AbstractAppFixtures $fixture
*/
protected function addFixture(AbstractAppFixtures $fixture) {
$add = true;
$activeFixtures = $this->getFixtureLoader()->getFixtures();
foreach ($activeFixtures as $activeFixture) {
if (get_class($activeFixture) === get_class($fixture)) {
$add = false;
}
}
if ($add) {
$this->getFixtureLoader()->addFixture($fixture);
if ($fixture instanceof DependentFixtureInterface) {
/** #var AbstractAppFixtures $parentFixture */
foreach ($fixture->getDependencies() as $parentFixture) {
if (class_exists($parentFixture)) {
$this->addFixture(new $parentFixture());
}
}
}
}
}
/**
*
*/
protected function executeFixtures() {
$this->getFixtureExecutor()->execute($this->getFixtureLoader()->getFixtures());
}
protected function getContainer() {
if (static::$container) {
return static::$container;
}
return static::$kernel->getContainer();
}
/**
* #return ORMExecutor
*/
private function getFixtureExecutor() {
if (!$this->fixtureExecutor) {
$em = $this->getEm();
$this->fixtureExecutor = new ORMExecutor($em, new ORMPurger($em));
}
return $this->fixtureExecutor;
}
/**
* #return ContainerAwareLoader
*/
private function getFixtureLoader() {
if (!$this->fixtureLoader) {
$this->fixtureLoader = new ContainerAwareLoader($this->getContainer());
}
return $this->fixtureLoader;
}
}

One thing you might want to consider is to introduce test data builder. Just because you have that dependency of Animal -> Zoo -> Owner doesn't mean that you have to deal with it in your tests. Of course, without having seen your tests, it's hard to tell. But I assume that, when testing the AnimalController, it is important that a valid Animal exists, not so much in which exact zoo with which exact owner. But even if that's the case, you could make your test data builders do the work for you.
The data builders don't deal with persistence, though. But this is a good thing, since you could use them for both, unit and functional tests.
I usually split up my test folder usually into Unit, Integration, Functional and Support. Support being support code for the tests. Regarding your question where to put the service, this would also be in Support. That being said, you probably won't even need the service you have thought of. The data builder create the entities you need in your tests, so you just have to persist it.

Related

Inject service based on dynamic value in Symfony

I have 2 services, BlueWorkerService and YellowWorkerService, both implementing the same interface, WorkerServiceInterface. Each of these services use the same entities but with different required logic.
I need to inject one of, but not both, of these classes and use them in ProcessorService so that the interface methods are called using on correct Worker. Which worker service to use is dependent on which Worker is currently being processed. I'll break it down:
Class WorkerProcessor {
private $workerService;
public function __construct(WorkerServiceInterface $workerServiceInterface)
{
$this->workerService = $workerServiceInterface;
}
public function getMixedColourWithRed() {
return $this->workerService->mixWithRed();
}
}
The worker service that is being used would be based on whether the worker being processed has the colour property of Blue or Yellow.
I know I can probably use a Factory to achieve this as described here but my problem is how to tell the factory which Worker colour I am processing?
Running on Symfony 3.4
If you need more info, just ask and I will update the question.
NOTE: I'm using Symfony 4.3.1. I'll post it like that, then I'll help you to move all code from this architecture to Symfony 3.4.
I'm using a similar concept to load different classes in my project. Let me explain first, then I'll add code under this text.
Firstly, I'm loading a custom compiler pass under src/Kernel.php (your file is app/AppKernel.php):
/**
* {#inheritDoc}
*/
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new BannerManagerPass());
}
BannerManagerPass its created under src/DependencyInjection/Compiler (in your case should be src/BUNDLE/DependencyInjection/Compiler`).
class BannerManagerPass implements CompilerPassInterface
{
/**
* {#inheritDoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->has(BannerManager::class)) {
return;
}
$definition = $container->findDefinition(BannerManager::class);
$taggedServices = $container->findTaggedServiceIds('banner.process_banners');
foreach (array_keys($taggedServices) as $id) {
$definition->addMethodCall('addBannerType', [new Reference($id)]);
}
}
}
As you see, this class should implement CompilerPassInterface. You can observe that I'm looking for specific services tagged as banner.process_banners. I'll show how I tagged services a little bit later. Then, I'm calling addBannerType method from BannerManager.
App\Service\BannerManager.php: (in your case src/BUNDLE/Service/BannerManager.php)
class BannerManager
{
/**
* #var array
*/
private $bannerTypes = [];
/**
* #param BannerInterface $banner
*/
public function addBannerType(BannerInterface $banner)
{
$this->bannerTypes[$banner->getType()] = $banner;
}
/**
* #param string $type
*
* #return BannerInterface|null
*/
public function getBannerType(string $type)
{
if (!array_key_exists($type, $this->bannerTypes)) {
return null;
}
return $this->bannerTypes[$type];
}
/**
* Process request and return banner.
*
* #param string $type
* #param Server $server
* #param Request $request
*
* #return Response
*/
public function process(string $type, Server $server, Request $request)
{
return $this->getBannerType($type)->process($request, $server);
}
}
This class has a custom method (created by me) called process(). You can name it whatever you want it, but I think that's pretty verbose. All parameters are sent by me, so don't mind. You can send whatever you want.
Now we have our Manager and compiler pass is set. It's time to set our banner types (based on my example) and tag them!
My banner types are under src/Service/Banner/Types (in your case should be src/BUNDLE/Service/WhateverYouWant/Type. This does not matter! You can change it later from services.yaml).
These types are implementing my BannerInterface. It does not matter the code under the class in this instance. One more thing that I should warn you! You should see that under BannerManager, inside the addBannerType() I'm calling $banner->getType(). This is one method inherited from BannerInterface in my case and it has a unique string (in my example I have three banner types: small, normal, large). This method can have any name, but don't forget to update it as well in your manager.
We are almost ready! We should tag them, then we are ready to try them!
Go to your services.yaml and add these lines:
App\Service\Banner\Types\:
resource: '../src/Service/Banner/Types/'
tags: [banner.process_banners]
Please see the tag!
Whatever I want to show a custom banner, I'm using a simple URL with $_GET where I keep my banner type, then I load it like this:
public function view(?Server $server, Request $request, BannerManager $bannerManager)
{
...
return $bannerManager->getBannerType($request->query->get('slug'))->process($request, $server);
}

$this->getUser()->isAdmin() VS $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')

I have to check if a user is an "admin" many times in my application.
I wonder which is the best way in terms of performance.
There are at least two (inside a controller):
$this->get('security.authorization_checker')->isGranted('ROLE_ADMIN');
$this->getUser()->isAdmin();
Users is an ORM class which maps a mysql table:
/**
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable
{
/**
* #ORM\Column(type="boolean", options={"default"=0})
*/
private $isAdmin = 0;
// [...]
public function isAdmin(){
return $this->isAdmin;
}
// [...]
public function getRoles()
{
if($this->isAdmin()) {
return ['ROLE_ADMIN', 'ROLE_USER'];
} else {
return ['ROLE_USER'];
}
}
}
I guess the getUser()->isAdmin() should be faster, unless the security system caches the roles in some ways...
EDIT: I ended up using $this->isGranted('ROLE_ADMIN'); since it's shorter to type
In terms of performance it depence. If performance is a important factor of your application you should profile it with blackfire. If your database is clustered and spread around the world, the method with the authorization_checker will be faster. If your DIC is to big, and your database lives on the same server, the isAdmin() method might be faster.
I've never use getUser() to read the role.
Use the SecurityContext, or an implementation of AccessDecisionManager instead, e.g.
$securityContext->isGranted('ROLE_USER');

How to get #request_stack service in app/console context?

I have services that require the #request_stack to fetch parameters.
Now, I want to expose certain functionality to console commands callable via ./app/console//. Yet in the context of an ./app/console, there is no #request_stack, yet one can input arguments.
In order to resolve this issue, I am now creating basically two services, one basic, only waiting for the params, and one being able to use the #request_stack.
Yet I dislike that there are two ways for the data to be fetched in the request-based flow and via the app/console.
Hence I am wondering, as I am simply want the data that comes per default via the request to also be able to be inputted via console arguments:
Can I setup a custom request_stack to simulate a request during a console command?
When I was investigating this issue, I stumbled across request stack push method, where a warning was already in place in the doc block:
/**
* Pushes a Request on the stack.
*
* This method should generally not be called directly as the stack
* management should be taken care of by the application itself.
*/
public function push(Request $request)
{
$this->requests[] = $request;
}
So while it would be possible to do it this way, I decided against the approach of my original question and to refactor my application instead.
I have created a context value object which just holds the parameter data:
/**
* Context
**/
class Context
{
/**
* #var string
*/
private $countryCode;
/**
* Context constructor.
* #param string $countryCode
*/
public function __construct($countryCode = '')
{
$this->countryCode = $countryCode;
}
/**
* #return string
*/
public function getCountryCode()
{
return $this->countryCode;
}
}
And a ContextFactory that creates the context with by the request stack:
class ContextFactory extends RequestAwareService
{
/**
* ContextFactory constructor.
* #param RequestStack $stack
*/
public function __construct(RequestStack $stack)
{
$this->setRequestStack($stack);
}
/**
* #return Context
*/
public function create()
{
return new Context($this->request->getCountryCode());
}
}
(The RequestAwareService is just a helper class to more easily parse the request.)
I then defined the services in my Bundle services.yml:
context.factory:
class: Kopernikuis\MyBundle\Service\Config\ContextFactory
arguments:
- '#request_stack'
context:
class: Kopernikuis\MyBundle\Service\Config\Context
factory:
- '#context.factory'
- create
Instead of injecting the #request_stack, I am now injecting my #context value object, which also had the benefit of reducing the hierarchy as now only one service parses the request_stack once, and I also noticed that certain functionality got much simpler as I could remove parameters from method calls, as they were all provided by the context object instead.
And in my custom commands, I can just replace my context
protected function execute(InputInterface $input, OutputInterface $output)
{
// #todo: use variable from InputInterface
$context = new Context('fnordfoo');
$this->getContainer()->set('context', $context);
}
With the newly gained knowledge, I strongly disagree with my original intent of trying to manually set the #request_stack.
Refactoring the code base to not necessarily require the #request_stack was a more solid choice.

Best way to create a test database and load fixtures on Symfony 2 WebTestCase?

I have a WebTestCase that executes some basic routes in my application.
I want to, on the setUp method of PHPUnit, create a test database identical to my main database, and load fixtures into it.
I'm currently doing some workaround and executing some console commands, something like this:
class FixturesWebTestCase extends WebTestCase
{
protected static $application;
protected function setUp()
{
self::runCommand('doctrine:database:create');
self::runCommand('doctrine:schema:update --force');
self::runCommand('doctrine:fixtures:load --purge-with-truncate');
}
protected static function runCommand($command)
{
$command = sprintf('%s --quiet', $command);
return self::getApplication()->run(new StringInput($command));
}
protected static function getApplication()
{
if (null === self::$application) {
$client = static::createClient();
self::$application = new Application($client->getKernel());
self::$application->setAutoExit(false);
}
return self::$application;
}
}
But I'm quite sure this is not the best approach, especially because the doctrine:fixtures:load expects the user to hit a Y char to confirm the action.
How can I solve that?
If you want to use doctrine:fixtures:load, you can use the --append option to avoid the user confirmation. Since you are recreating the database every time, purging is unnecessary. I used to use doctrine fixtures alone for testing, but have since switched to using fixtures & LiipFunctionalTestBundle to avoid DRY. This bundle makes fixtures easier to manage.
EDIT: David Jacquel's answer is the correct one for loading Doctrine Fixtures:
doctrine:fixtures:load --no-interaction
or
doctrine:fixtures:load -n
In order to bypass user confirmation you can use
doctrine:fixtures:load --no-interaction
or
doctrine:fixtures:load -n
UPDATED ANSWER
You can create a base class for your test cases which makes fixture loading easy by leveraging some classes from the Doctrine Data Fixtures library. This class would look pretty much like this:
<?php
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
abstract class FixtureAwareTestCase extends KernelTestCase
{
/**
* #var ORMExecutor
*/
private $fixtureExecutor;
/**
* #var ContainerAwareLoader
*/
private $fixtureLoader;
public function setUp()
{
self::bootKernel();
}
/**
* Adds a new fixture to be loaded.
*
* #param FixtureInterface $fixture
*/
protected function addFixture(FixtureInterface $fixture)
{
$this->getFixtureLoader()->addFixture($fixture);
}
/**
* Executes all the fixtures that have been loaded so far.
*/
protected function executeFixtures()
{
$this->getFixtureExecutor()->execute($this->getFixtureLoader()->getFixtures());
}
/**
* #return ORMExecutor
*/
private function getFixtureExecutor()
{
if (!$this->fixtureExecutor) {
/** #var \Doctrine\ORM\EntityManager $entityManager */
$entityManager = self::$kernel->getContainer()->get('doctrine')->getManager();
$this->fixtureExecutor = new ORMExecutor($entityManager, new ORMPurger($entityManager));
}
return $this->fixtureExecutor;
}
/**
* #return ContainerAwareLoader
*/
private function getFixtureLoader()
{
if (!$this->fixtureLoader) {
$this->fixtureLoader = new ContainerAwareLoader(self::$kernel->getContainer());
}
return $this->fixtureLoader;
}
}
Then, in your test case, simply extend the above class and before your test add all the needed fixtures and execute them. This will automatically purge your database before loading fixtures. Example follows:
class MyTestCase extends FixtureAwareTestCase
{
public function setUp()
{
parent::setUp();
// Base fixture for all tests
$this->addFixture(new FirstFixture());
$this->addFixture(new SecondFixture());
$this->executeFixtures();
// Fixtures are now loaded in a clean DB. Yay!
}
}
OLD ANSWER
(I decided to "deprecate" this answer because it only explains how to clean up the database without telling how to load fixtures after).
There's an even cleaner way of accomplishing this without having to run commands. It basically consists in using a combination of the SchemaTool and the ORMPurger. You can create an abstract base class which performs this kind of operations to avoid repeating them for each specialized test case. Here's a code example of a test case class which sets up database for a generic test case:
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\Tools\SchemaTool;
abstract class DatabaseAwareWebTestCase extends WebTestCase {
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
$kernel = static::createKernel();
$kernel->boot();
$em = $kernel->getContainer()->get('doctrine')->getManager();
$schemaTool = new SchemaTool($em);
$metadata = $em->getMetadataFactory()->getAllMetadata();
// Drop and recreate tables for all entities
$schemaTool->dropSchema($metadata);
$schemaTool->createSchema($metadata);
}
protected function tearDown() {
parent::tearDown();
$purger = new ORMPurger($this->getContainer()->get('doctrine')->getManager());
$purger->setPurgeMode(ORMPurger::PURGE_MODE_TRUNCATE);
$purger->purge();
}
}
This way, before running each test case which inherits from the above class, the database schema will be rebuilt from scratch, then cleaned up after every test run.
Hope this helps.
I've stumbled upon a really neat bundle named Doctrine-Test-Bundle
Instead of creating and dropping schema on every test it simply rollback.
My Tests went from 1m40s to.. 2s. And it's isolated.
All you need is a clear test database and it'll do the trick.
I used this command:
yes | php app/console doctrine:fixtures:load --purge-with-truncate
But of course LiipFunctionalTestBundle looks promising.
I wanted to load all your fixtures like the doctrine:fixtures:load command does. I didn't want to run exec from inside the test case because it seemed like a messy way to do things. I looked at how the doctrine command does this itself and just copied over the relevant lines.
I extended from the Symfony WebTestCase and after the Kernel was created I just called my method which works exactly like the Doctrine load-fixtures command.
/**
* Load fixtures for all bundles
*
* #param Kernel $kernel
*/
private static function loadFixtures(Kernel $kernel)
{
$loader = new DataFixturesLoader($kernel->getContainer());
$em = $kernel->getContainer()->get('doctrine')->getManager();
foreach ($kernel->getBundles() as $bundle) {
$path = $bundle->getPath().'/DataFixtures/ORM';
if (is_dir($path)) {
$loader->loadFromDirectory($path);
}
}
$fixtures = $loader->getFixtures();
if (!$fixtures) {
throw new InvalidArgumentException('Could not find any fixtures to load in');
}
$purger = new ORMPurger($em);
$executor = new ORMExecutor($em, $purger);
$executor->execute($fixtures, true);
}
Just recently the bundle hautelook/AliceBundle expose two traits to help you solve the use case of loading fixtures in functional tests: RefreshDatabaseTrait and ReloadDatabaseTrait.
From the doc:
namespace App\Tests;
use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class NewsTest extends WebTestCase
{
use RefreshDatabaseTrait;
public function postCommentTest()
{
$client = static::createClient(); // The transaction starts just after the boot of the Symfony kernel
$crawler = $client->request('GET', '/my-news');
$form = $crawler->filter('#post-comment')->form(['new-comment' => 'Symfony is so cool!']);
$client->submit($form);
// At the end of this test, the transaction will be rolled back (even if the test fails)
}
}
And you are good !

Symfony2 and Doctrine - return database result as array of arrays in controller

Doing this in a symfony2 controller results in an array where each single result is itself an object.. but i need it to be an array as well so i can easily json_encode the whole list
$em->getRepository('MyBundle:Report')->findByEvaluation($evaluation_id, \Doctrine\ORM\Query::HYDRATE_ARRAY)
So how do i get an array of arrays and not an array of objects which is what i'm getting by using HYDRATE_ARRAY?
I ended up installing the JMSSerializerBundle which provides the methods I needed. Since I had a linked entity and I did not need the linked object, in my entity class i now have
...
use JMS\SerializerBundle\Annotation\ExclusionPolicy;
use JMS\SerializerBundle\Annotation\Exclude;
/**
* Iddp\RorBundle\Entity\Report
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Iddp\RorBundle\Entity\ReportRepository")
* #ExclusionPolicy("None")
*/
class Report
By adding the #ExclusionPolicy("None") all properties should be serialized except those marked with exclude. Then, in the linked entitites in the same class just add the exclude annotation
/**
* #ORM\ManyToOne(targetEntity="Client", inversedBy="reports")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
* #Exclude
*/
protected $client;
and so in the controller i can now do (after adding use Symfony\Component\HttpFoundation\Response;)
$serializer = $this->container->get('serializer');
$reports = $serializer->serialize($reports, 'json');
return new Response($reports);
And that's that. Symfony+Doctrine can make some simple things complicated.
There's no way, out-of-the-box, to tell Doctrine to treat an object as an array. However, with a little tweaking and some questionable design decisions, you can achieve this.
The first thing you need to do, is create a base class (for your classes to extend) that implements the ArrayAccess interface. An article describing this can be found in the Doctrine cookbook. It will look something like this:
abstract class DomainObject implements ArrayAccess
{
public function offsetExists($offset)
{
return isset($this->$offset);
}
public function offsetSet($offset, $value)
{
throw new BadMethodCallException(
"Array access of class " . get_class($this) . " is read-only!"
);
}
public function offsetGet($offset)
{
return $this->$offset;
}
public function offsetUnset($offset)
{
throw new BadMethodCallException(
"Array access of class " . get_class($this) . " is read-only!"
);
}
}
Then, when you create your model classes (or at least the ones that you want to treat as arrays), you'll need to extend this DomainObject class. The last piece of the puzzle is to make your class properties public in order to give the json_encode function the ability to inspect your class properties and use them as keys for the json object.
NB: using public properties in classes can lead to a lot of hard to trace bugs and is generally considered questionable practice. this is just an example that i whipped up quickly to illustrate how it could be accomplished. I'm sure there is a more elegant way to implement this that doesn't require public properties. This solution is just meant to get the ball rolling
An example domain class might look something like this:
class Tester extends DomainObject
{
public $foo;
public $bar;
public function __construct($foo, $bar)
{
$this->foo = $foo;
$this->bar = $bar;
}
}
Now you'll be able to cast an instance of the Tester class to an array and pass that array to json_encode:
$test = new Tester('Hello', 'World');
echo json_encode((array)$test);
Which will produce the following output:
{"foo":"Hello","bar":"World"}
EDIT: just to bring your code snippet back into context. You don't need to use HYDRATE_ARRAY anymore, it would just look like this:
$results = $em->getRepository('MyBundle:Report')->findByEvaluation($evaluation_id);
foreach ($results as $result) {
echo json_encode((array)$result);
}
provided your Report class extends the DomainObject class defined above.

Resources