Buy

Controlling Data / Fixtures in a Test

When we try running the test again, it fails! What the heck?

If we did a little debugging we’d see that it fails the second time because the username and email in the test are already taken. So instead of success, we see a validation error.

This is a very important detail about testing. Before running any test, we need to make sure the database is in a predictable state. Our test will pass... unless there happens to already be a user5 in the database.

Deleting Users

To fix this, let’s empty the user table before running the test. Start by grabbing the container object, which is stored statically on the parent test class:

// src/Yoda/UserBundle/Tests/Controller/RegisterControllerTest.php
// ...

public function testRegister()
{
    $client = static::createClient();

    $container = self::$kernel->getContainer();
    // ...
}

Now, grab the Doctrine entity manager by getting the doctrine service and calling getManager. If you’re not comfortable with what I just did, don’t worry. I’ll demystify this in a moment:

// src/Yoda/UserBundle/Tests/Controller/RegisterControllerTest.php
// ...

public function testRegister()
{
    $client = static::createClient();

    $container = self::$kernel->getContainer();
    $em = $container->get('doctrine')->getManager();

    // ...
}

Once we have the entity manager we can get the UserRepository. Build a query from the repository that deletes all of the users:

// src/Yoda/UserBundle/Tests/Controller/RegisterControllerTest.php
// ...

public function testRegister()
{
    // ...
    $em = $container->get('doctrine')->getManager();
    $userRepo = $em->getRepository('UserBundle:User');
    $userRepo->createQueryBuilder('u')
        ->delete()
        ->getQuery()
        ->execute()
    ;

    // ... the actual test
}

Re-run the test again to see that everything passes. Now this is a good-looking test!

Notice that we’re not testing for every tiny detail. That would be a ton of work and functional tests aren’t meant to replace us as developers from making sure things work when we build them.

Instead, we just want to see that the form can be submitted successfully and unsuccessfully. The important thing is that if we break something major later, our test will let us know.

Separating the dev and test Databases

When we run our tests, it’s emptying the same user table we’re using for development! That’s kind of annoying! Instead, let’s use 2 different databases: one for development and one that’s used only by the tests.

Open up the main config.yml file and find the doctrine database configuration. Copy and paste it into the config_test.yml file, but remove everything except for the dbname option. Now, add an _test to the end of it:

# app/config/config_test.yml
# ...

doctrine:
    dbal:
        dbname:   %database_name%_test

Tip

The database_name is a parameter, which lives in app/config/parameters.yml.

This little trick takes advantage of how Symfony environments work. The test environment uses all of the normal Doctrine configuration, except that it overrides this one option.

Don’t forget to setup the new database by running doctrine:database:create. Pass an extra --env=test option so that the command runs in the test environment. Use the same idea to insert the schema:

php app/console doctrine:database:create --env=test
php app/console doctrine:schema:create --env=test

Tip

By default, all app/console commands run in the dev environment.

You can now re-run the tests knowing that our main database isn’t being affected:

php bin/phpunit -c app

Behat

As cool as this is, in reality we use a tool called Behat instead of Symfony’s built in functional testing tools. And you’re in luck because everything you just learned translates to Behat. Check out our tutorial on this to take your functional testing into space!

Do this or risk an angry phone call from Darth Vader when the super laser doesn’t fire because you added a new espresso machine to the breakroom.

Leave a comment!

  • 2016-08-29 weaverryan

    That's right Tael! Just surround any % characters with quotes and you'll be just fine now and going forward :)

  • 2016-08-29 Tael Kim

    NOTE THAT: yml config syntax will be changed.
    Not quoting a scalar starting with the "%" indicator character is deprecated since Symfony 3.1 and will throw a ParseException in 4.0

  • 2015-12-15 weaverryan

    Hi Sarang!

    Ah, this is actually pretty cool! When something changes in Symfony (e.g. from version 2.7 to 2.8), instead of just changing it and breaking your code, Symfony deprecates the old functionality. AND, it also tells you about any deprecated code paths when you run your tests so you know about them and can upgrade :). Some things that we show in this tutorial are deprecated now (we're working on new versions currently), and this is a great example. In RegisterFormType, you should rename setDefaultOptions to configureOptions - here's an example (look at the first 2 code blocks): https://github.com/symfony/sym...

    When you fix the deprecation, your tests will pass :). This is actually one of the *killer* features of Symfony. If you want to learn more about it, this is a great presentation about it: http://www.slideshare.net/nico...

    Cheers!

  • 2015-12-15 Sarang Bondre

    Am getting this error

    UserBundle\Tests\Controller\RegisterControllerTest::testRegister

    Failed asserting that false is true.

    FAILURES!

    Tests: 3, Assertions: 7, Failures: 1.

    Remaining deprecation notices (2)

    UserBundle\Form\RegisterFormType: The FormTypeInterface::setDefaultOptions() method is deprecated since version 2.7 and will be removed in 3.0. Use configureOptions() instead. This method will be added to the FormTypeInterface with Symfony 3.0: 2x

    2x in RegisterControllerTest::testRegister from UserBundle\Tests\Controller

  • 2015-11-23 docLommy

    Even GREATER answer. Smooth and elegant solution - I'll try that. Thnxbye :)

  • 2015-11-20 weaverryan

    GREAT question. Unless you're using the "symfony2" driver via the Symfony2Extension (which I don't use), Behat is making *real* HTTP requests. So, you'll need to create a new app_test.php file that boots Symfony in the "test" environment. Then, go into your config_test.yml file and override doctrine's "dbname" key to be a different database name. Finally, update behat.yml to use this app_test.php in your base URL - like localhost:8000/app_test.php

    Does that make sense?

  • 2015-11-20 docLommy

    Hiya ! How can I target my test database via behat ? Is there a specific behat config file for this ? Or do I reference a specific test environment when running behat ?