4

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:

  1. The user add some products in its cart
  2. The user remove one product from its cart
  3. 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.

Community
  • 1
  • 1
A.L
  • 10,259
  • 10
  • 67
  • 98

1 Answers1

2

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.

A.L
  • 10,259
  • 10
  • 67
  • 98
Matteo
  • 37,680
  • 11
  • 100
  • 115
  • What if I also want to test the Users with `john` and `bob` firstnames? – A.L Feb 18 '15 at 14:01
  • Write a dedicated fixtures class for john and load it in the dedicated test class – Matteo Feb 18 '15 at 14:20
  • 1
    Should I copy and paste all the code from the `LoadSimpleUserData` class? Is there no better method? – A.L Feb 18 '15 at 14:55
  • You can of course make a hierarchy with common method for create user and so on, but are only fixture class for test pourpose, obviously are alway class that must manage the project evolution (new mandatory field and so on). I post only example code but in my project i use a minimal hierarchy for create generic test user and call directly the method like `createTestUSer($name,$surname)` – Matteo Feb 18 '15 at 14:58
  • @A.L PS: congrats for the truebadges app! Really nice! – Matteo Feb 18 '15 at 15:59
  • Do you suggest me to use table inheritance to add the user in the database in a base class, then modify it with the values I want by using the inheriting class? I haven't tried it yet. – A.L Feb 18 '15 at 16:18
  • No sorry, i want to say that you can use inheritance in the FixtureLoader that implement the method for create the user then extends it with only the call to it for create the user – Matteo Feb 18 '15 at 16:20
  • I'm sorry, I was thinking to use inheritance with the fixtures classes too. Can you please provide a small example ? – A.L Feb 18 '15 at 16:21
  • of course, i focused the example on the relationships between the entity, but i try to explain better what i mean. I will update the answer asap – Matteo Feb 18 '15 at 16:26
  • 1
    @A.L I have updated my answer, i don't know is now i more clear what i mean – Matteo Feb 18 '15 at 16:53