Independent functional tests with LiipFunctionalTestBundle and fixtures? - symfony

I'm adding tests to a Symfony2 project. Previously I used the same database for dev and test environments, it used a MySQL database already populated with the same data than on the production server.
The tests were working dependently because some tests depended on previous tests. For example if I had a shop website, I added a product in the cart then removed the product from the cart. So I needed to insert data by using a form, before being able to remove it.
Now I want to work with independent functional tests, because that's the recommended way (by one of Symfony2's developers).
I've configured LiipFunctionalTestBundle correctly to use a SQLite database in the test environment and I've started to add fixtures with DoctrineFixturesBundle.
But I don't know how much data I have to load for each functional test. What fixture should I load at the beginning of a test? How to deal with CRUD operations when the entity depends on other entities because of relationships between tables?
Let's say I'm developing a shop, I want a few tests:
The user add some products in its cart
The user remove one product from its cart
The user order the remaining products
Should I create a different fixture for every step? It means that my fixtures will need to exist in many different states: empty cart, cart with one product ordered, etc. It seems correct to me but very time consuming, so I'm wondering if my idea is valid.

For each test case is better to load less fixture as possible both for isolation and for performance (the test suite can go very slowly).
When fixture depends each other, you simply manage them with the doctrine reference and link each other, take care of the order also.
As Example, suppose the simply user and role relations.
A generic class for manage role fixture:
abstract class BaseLoadRoleData extends AbstractFixture implements OrderedFixtureInterface
{
public function getOrder()
{
return 1;
}
protected function createRole(ObjectManager $manager, $rolename)
{
$role= new Role();
$role->setName($rolename);
$manager->persist($role);
$manager->flush();
$this->setReference('role-' . $rolename, $role);
}
}
A Dedicated class for the Simple Role
class LoadSimpleRoleData extends BaseLoadRoleData
{
public function load(ObjectManager $manager)
{
$this->createRole($manager, Role::SIMPLE);
}
}
A Dedicated class for the Admin Role
class LoadAdminRoleData extends BaseLoadRoleData
{
public function load(ObjectManager $manager)
{
$this->createRole($manager, Role::ADMIN);
}
}
And the user:
A generic class for manage user fixture:
abstract class BaseLoadUserData extends AbstractFixture implements OrderedFixtureInterface
{
/**
* #var ContainerInterface
*/
private $container;
/**
* {#inheritDoc}
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function getOrder()
{
return 2;
}
protected function buildUser($username, $firstName = "",$lastName ="")
{
$user= new User();
$user->setUsername($username);
$user->setFirstName($firstName);
$user->setLastName($lastName);
return $user;
}
}
A Dedicated class for the Simple User
class LoadSimpleUserData extends BaseLoadUserData {
/**
* Load data fixtures with the passed EntityManager
*
* #param Doctrine\Common\Persistence\ObjectManager $manager
*/
function load(ObjectManager $manager)
{
$user = $this->buildUser($manager, "simple#example.com");
$user->addRole($this->getReference('role-'.Role::SIMPLE));
$manager->persist($user);
$manager->flush();
$this->setReference('user-' . "admin#example.com", $user);
}
}
A Dedicated class for the Admin User
class LoadAdminUserData extends BaseLoadUserData {
/**
* Load data fixtures with the passed EntityManager
*
* #param Doctrine\Common\Persistence\ObjectManager $manager
*/
function load(ObjectManager $manager)
{
$user = $this->buildUser($manager, "admin#example.com");
$user->addRole($this->getReference('role-'.Role::ADMIN));
$manager->persist($user);
$manager->flush();
$this->setReference('user-' . "admin#example.com", $user);
}
Now you can use it separately, as example, based on the Liip Functional Test Bundle:
class LoginControllerTest {
public function testAdminUserLogin()
{
$this->loadFixtures(array(
'Acme\DemoBundle\DataFixtures\ORM\LoadAdminRoleData',
'Acme\DemoBundle\DataFixtures\ORM\LoadAdminUserData'
));
// you can now run your functional tests with a populated database
$client = static::createClient();
// ...
// test the login with admin credential
}
public function testSimpleUserLogin()
{
// add all your fixtures classes that implement
// Doctrine\Common\DataFixtures\FixtureInterface
$this->loadFixtures(array(
'Acme\DemoBundle\DataFixtures\ORM\LoadSimpleRoleData',
'Acme\DemoBundle\DataFixtures\ORM\LoadSimpleUserData'
));
// you can now run your functional tests with a populated database
$client = static::createClient();
// ...
// test the login with simple user credential
}
}
Hope this help.

Related

Phpunit reusable test methods in 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.

Multiple files for Doctrine Fixtures for Symfony 3

I'm using the bundle Doctrine:Fixtures to load an example of bbdd throw the Entitys and since I work alone in the project it's ok.
But now, I got a colleague in my project and we were wondering if it's possible to set up a different files for the loading.
I got my own file of fixtures associated in git and I don't want him modifying this file. I would like to have an special file just for him that will allows him to modify whenever he wants this file of fixtures. So, anyone can have his owns records in the init of the bbdd.
If it's not possible with multiple files, could be possible in another way?
http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
You can load specific fixture files using the --fixture flag:
php app/console doctrine:fixtures:load --fixture=/src/BundleName/DataFixtures/ORM/Fixture.php
Or, you could add a Fixtures.php.dist file with some working examples, then use .gitignore to ignore Fixtures.php.
Then add a command into your build (or in composer scripts), and/or your documentation to copy this .dist file to Fixtures.php when checking out the project.
Another method if you're doing BDD is to create a Helper class that can be used in your Context to create and persist entities as you need them in tests. This would allow you to create only specifics needed for the test. All it really needs is the EntityManager so it may be simpler than pre-defining all the fixtures up front.
You can use Faker to generate realistic entities.
class AbstractFixtureHelper implements ContainerAwareInterface
{
/**
* #var Generator
*/
protected $faker;
/**
* #var ContainerInterface
*/
protected $container;
public function __construct()
{
$this->faker = Factory::create();
}
/**
* #param ContainerInterface|null $container
* #return void
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
/**
* #return EntityManager
*/
protected function getEntityManager()
{
return $this->container->get('doctrine.orm.entity_manager');
}
}
Then for different entities - in this example, a user:
class UserFixtureHelper extends AbstractFixtureHelper
{
public function createUser()
{
$user = new User();
$user->setEmail($this->faker->email);
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
return $user;
}
}
Then in your Context, inject the UserFixtureHelper and create directly in the scenario steps.
/**
* #Given there is a User who XXX
*/
public function thereIsAUser()
{
$user = $this->userFixtureHelper->createUser();
}

How to Get Current User inside Repository Symfony 2.7

All of my query in Entity Repository needs to be filtered by user.
Now I want to know how can I access the currently logged in user in Entity Repository directly.
What I did today is to get the currently logged in user in my controller, through the use of $this->getUser() and then pass it to Entity Repository and this is not efficient.
You need to inject security.token_storage service into another one to get the current user, but as of Repository classes belong to Doctrine project, not Symfony, it is not recommended to do this.. May be there is a way to achieve it by creating custom entityManager class as described here, but I don't think it would a good solution..
Instead of customizing an entityManager better create a service which calls repository classes' methods, inject desired services into it.. Let Repository classes do their job.
Implementation would be something like this:
RepositoryClass:
class MyRepository extends EntityRepository
{
public function fetchSomeDataByUser(UserInterface $user)
{
// query
}
}
Service:
class MyService
{
private $tokenStorage;
public function _construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
// other services
}
public function getSomeDataByUser()
{
$user = $this->tokenStorage->getToken()->getUser();
return $this->entityManager->getRepository(MyREPOSITORY)->fetchSomeDataByUser($user);
}
}
Usage:
public function someAction()
{
$dataByUser = $this->get(MYSERVICE)->getSomeDataByUser();
}
If you use JMSDiExtraBundle it can be done by adding setter injection:
use Doctrine\ORM\EntityRepository;
use JMS\DiExtraBundle\Annotation as DI;
class YourRepository extends EntityRepository
{
/** #var User current user entity */
protected $user;
/**
* #DI\InjectParams({
* "token_storage" = #DI\Inject("security.token_storage")
* })
*/
public function setSimplaManager(TokenStorageInterface $tokenStorage)
{
$token = $tokenStorage->getToken();
if (!is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return;
}
$this->user = $user;
}
}

Symfony2 best practices for stay DRY

I'm new to Symfony2. I have to learn it for my new job (it starts this monday). Before that, I used a lot CodeIgniter... so this change a bit.
After reading tons of documentations, tuts, best practices ... create my Own intranet for testing (customers has websites, websites has accesses, accesses has website, website has category, accesses has accesscategory) I still have some questions.
First Question :
When you have a website with frontend and backend you have all the time some repetitives actions like :
- create new entity
- read entity
- update entity
- delete entity
...
In CI, I create a BaseController and a BaseModel and with some extends, I was OK.
This practice is still OK for Symfony 2 or do Symfony have another way to handle that ?
Like AppBundle\Controller\BaseController extended by a AppBundle\Controller\AdminController (and FrontController) extended by AppBundle\Controller\MyEntityController ?
Because Actually, each time, in each controller I have the same code. When I edit an entity (for example), it's the same process : load the entity by id, throw exception if no entity, create and hydrate the form, handleRequest the post and valid the form, reidrect or display the view... but... I always cut/paste the same code... aweful T__T
So I'm searching for the best way to handle that
** Second Question : **
What is the best and elegent way to work with the DoctrineManager ?
Do I have to call it, each time in my actions ? $em = $this->get... or, can I create something like MyEntityManager which call the EntityManager and the repository of my entity ?
Actually, this is what I do :
I create an abstract AppBundle\Manager\BaseManager with loadAndFlush
<?php
namespace AppBundle\Manager;
abstract class BaseManager
{
protected function persistAndFlush($entity)
{
$this->em->persist($entity);
$this->em->flush();
}
}
Then, for each Entity, I create his own manager :
<?php
namespace AppBundle\Manager;
use Doctrine\ORM\EntityManager;
use AppBundle\Manager\BaseManager;
use AppBundle\Entity\Customer;
class CustomerManager extends BaseManager
{
/**
* #var EntityManager
*/
protected $em;
/**
* #param EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* #param $customerId
* #return null|object
*/
public function loadCustomer($customerId)
{
return $this->getRepository()
->findOneBy(array('id' => $customerId));
}
/**
* #param Customer $customer
*/
public function saveCustomer(Customer $customer)
{
$this->persistAndFlush($customer);
}
/**
* #return \Doctrine\ORM\EntityRepository
*/
public function getRepository()
{
return $this->em->getRepository('AppBundle:Customer');
}
}
Then, I define this manager as a service :
parameters:
app.customer_manager.class: AppBundle\Manager\CustomerManager
services:
app.customer_manager:
class: %app.customer_manager.class%
arguments: [#doctrine.orm.entity_manager]
And Then I use the service in my Controller :
/**
* #Route("/edit/{customerId}", name="customer_edit")
* #Security("has_role('ROLE_ADMIN')")
*/
public function editAction($customerId, Request $request)
{
if (!$customer = $this->get('app.customer_manager')->loadCustomer($customerId)) {
throw new NotFoundHttpException($this->get('translator')->trans('This customer does not exist.'));
}
$form = $this->get('form.factory')->create(new CustomerType(), $customer);
if($form->handleRequest($request)->isValid()) {
$this->get('app.customer_manager')->saveCustomer($customer);
$request->getSession()->getFlashBag()->add('notice', 'Client bien enregistré.');
return $this->redirect(
$this->generateUrl(
'customer_show', array(
'customerId' => $customer->getId()
)
)
);
}
return $this->render('default/customer/add.html.twig', array(
'form' => $form->createView(),
'customer' => $customer
));
}
Is it a good practice, is it too complicated ? Is there any better other way to process in symfony ?
For first question Symfony2 provides CRUD Generator, take a look at this.
For second one you should use Repository Pattern provided by framework, for more information about this checkout following links:
http://msdn.microsoft.com/en-us/library/ff649690.aspx
http://symfony.com/doc/current/book/doctrine.html#custom-repository-classes

Symfony2: SonataAdminBundle - How can i get the object representing the current user inside an admin class?

I use the sonata-admin bundle.
I have the relationship with the user (FOSUserBundle) in the PageEntity.
I want to save the current user which create or change a page.
My guess is get the user object in postUpdate and postPersist methods of the admin class and this object transmit in setUser method.
But how to realize this?
On the google's group I saw
public function setSecurityContext($securityContext) {
$this->securityContext = $securityContext;
}
public function getSecurityContext() {
return $this->securityContext;
}
public function prePersist($article) {
$user = $this->getSecurityContext()->getToken()->getUser();
$appunto->setOperatore($user->getUsername());
}
but this doesn't work
In the admin class you can get the current logged in user like this:
$this->getConfigurationPool()->getContainer()->get('security.token_storage')->getToken()->getUser()
EDIT based on feedback
And you are doing it this? Because this should work.
/**
* {#inheritdoc}
*/
public function prePersist($object)
{
$user = $this->getConfigurationPool()->getContainer()->get('security.token_storage')->getToken()->getUser();
$object->setUser($user);
}
/**
* {#inheritdoc}
*/
public function preUpdate($object)
{
$user = $this->getConfigurationPool()->getContainer()->get('security.token_storage')->getToken()->getUser();
$object->setUser($user);
}
Starting with symfony 2.8, you should use security.token_storage instead of security.context to retrieve the user. Use constructor injection to get it in your admin:
public function __construct(
$code,
$class,
$baseControllerName,
TokenStorageInterface $tokenStorage
) {
parent::__construct($code, $class, $baseControllerName);
$this->tokenStorage = $tokenStorage;
}
admin.yml :
arguments:
- ~
- Your\Entity
- ~
- '#security.token_storage'
then use $this->tokenStorage->getToken()->getUser() to get the current user.
I was dealing with this issue on the version 5.3.10 of symfony and 4.2 of sonata. The answer from greg0ire was really helpful, also this info from symfony docs, here is my approach:
In my case I was trying to set a custom query based on a property from User.
// ...
use Symfony\Component\Security\Core\Security;
final class YourClassAdmin extends from AbstractAdmin {
// ...
private $security;
public function __construct($code, $class, $baseControllerName, Security $security)
{
parent::__construct($code, $class, $baseControllerName);
// Avoid calling getUser() in the constructor: auth may not
// be complete yet. Instead, store the entire Security object.
$this->security = $security;
}
// customize the query used to generate the list
protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
{
$query = parent::configureQuery($query);
$rootAlias = current($query->getRootAliases());
// ..
$user = $this->security->getUser();
// ...
return $query;
}
}

Resources