Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Controlling the Database

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

Eventually, you'll need to insert test data at the beginning of your scenarios. And that's when things get tricky. So, let's learn to do this right.

First: never assume that there is data in the database at the beginning of a scenario. Instead, you should put any data you need there intentionally with a Given.

Go to the top of this scenario and add:

... lines 1 - 6
Given there is an admin user "admin" with password "admin"
... lines 8 - 14

I'm inventing this language - it describes something that needs to be setup using natural language. Change the Given line below this to And for readability:

... lines 1 - 7
And I am on "/"
... lines 9 - 14

Run it again: it should print out the step definition for this new language.

./vendor/bin/behat features/web/authentication.feature

And it does! Copy that and put it into our FeatureContext class. Change :arg1 to :username and :arg2 to :password. Update the arguments to match:

... lines 1 - 41
/**
* @Given there is an admin user :username with password :password
*/
public function thereIsAnAdminUserWithPassword($username, $password)
{
... lines 47 - 54
}
... lines 56 - 87

To fill this in, we want to insert a new user in the database with this username and password. If Symfony were loaded, that would be really easy: I'd create a User object, set some data then persist and flush it. So let's do that! Even if you're not using Symfony, you'll use the same basic process to bootstrap your application to get access to all of your normal useful objects and functions.

Bootstrapping Symfony in FeatureContext

We only need to boot Symfony once at the beginning of the test suite. Afterwards all of our scenarios will be able to access Symfony's container. Make a new public function bootstrapSymfony():

... lines 1 - 31
public static function bootstrapSymfony()
{
... lines 34 - 39
}
... lines 41 - 87

And inside, we'll do exactly what its name says.

We'll need a couple of require statements for autoload.php and the AppKernel.php class:

... lines 1 - 33
require_once __DIR__.'/../../app/autoload.php';
require_once __DIR__.'/../../app/AppKernel.php';
... lines 36 - 87

Then, it's as easy as $kernel = new AppKernel();. Pass it the environment - test - and the debug value - true - so we can see errors. Finish with $kernel->boot();:

... lines 1 - 36
$kernel = new AppKernel('test', true);
$kernel->boot();
... lines 39 - 87

Congrats - you just bootstrapped your Symfony app.

What we really want is access to the service container. To get that, create a new private static $container; property:

... lines 1 - 15
private static $container;
... lines 17 - 87

Then in the method, set that with self::$container = $kernel->getContainer();:

... lines 1 - 38
self::$container = $kernel->getContainer();
... lines 40 - 87

Now, as long as we call bootstrapSymfony() first, we'll have access to the container. Oh, and update the method to be a public static function:

... lines 1 - 31
public static function bootstrapSymfony()
... lines 33 - 87

I'm making this all static because it allows us to have one container across all of our different scenarios. Because remember, each scenario gets its own context instances.

We could call this method manually, but that's not fancy! Remember the hook system? We used @BeforeScenario and @AfterScenario before, but there are other hooks, like @BeforeSuite. Let's use that!

... lines 1 - 28
/**
* @BeforeSuite
*/
public static function bootstrapSymfony()
... lines 33 - 87

Behat will call this method one time, even if we're testing 10 features and 100 scenarios.

Saving a New User

Inside of thethereIsAnAdminUserWithPassword() step definition, let's go to work! I already have a User entity setup in the project, so we can say $user = new User(). Then set the username and the "plainPassword":

... lines 1 - 46
$user = new \AppBundle\Entity\User();
$user->setUsername($username);
$user->setPlainPassword($password);
... lines 50 - 87

I have a Doctrine listener already setup that will encode the password automatically. Which is good: it's well-known that raptors can smell un-encoded passwords...

In this app, to make this an "admin" user, we need to give the user ROLE_ADMIN:

... lines 1 - 49
$user->setRoles(array('ROLE_ADMIN'));
... lines 51 - 87

Now the moment of truth: we need the entity manager. Cool! Grab it with $em = self::$container->get('doctrine') (to get the Doctrine service) and then ->getManager();:

... lines 1 - 51
$em = self::$container->get('doctrine')->getManager();
... lines 53 - 87

It's easy from here: $em->persist($user); and $em->flush();:

... lines 1 - 52
$em->persist($user);
$em->flush();
... lines 55 - 87

And that should do it!

We already have a user called admin in the database, so let's test this using admin2. Give it a go!

./vendor/bin/behat features/web/authentication.feature

This should not work, since this user isn't in the database yet... unless it's created by the code we just wrote! Brilliant!

This is huge: we're guaranteeing there's an admin2 user by bootstrapping our app and being dangerous with all of our useful services.

Leave a comment!

16
Login or Register to join the conversation
Soltan Avatar

I have no /app/ directory in my framework, how should I download it?
require_once __DIR__.'/../../app/autoload.php'; => this line is not compiling because of missing /app/

Reply

Hey Soltan

That "app" folder doesn't exist anymore since Symfony4, so don't worry if you dont have it. Instead of requiring app/autoload.php you should require composer's autoload -> rootApp/vendor/autoload.php

Cheers!

Reply
Soltan Avatar

I installed Behat\Symfony2Extension: as in tutorial but when I run feature file I have this error:
Class AppKernel does not exist
How can I fix it?

Reply

Ohh, yes, you have to specify from where to load the Kernel. You need to add something like this into your behat.yml file


  extensions:
    Behat\Symfony2Extension:
      kernel:
        class: App\AppKernel   # Adjust this to your needs (This is the FQNS of your Kernel class) 
Reply
Default user avatar
Default user avatar Wahab Qureshi | posted 4 years ago

Think thats a bit too many steps to follow. use this extension to make your lives easier http://inevitabletech.uk/bl...

Reply
Default user avatar

Hey Ryan,

I've just started a Symfony 4 app (actually skeleton on 3.4). Is this still the "right way" to handle persisting users before testing them?

Cheers,

Tom

Reply

Hey tomhv

There is an easier way to setup your Symfony's container by installing & configuring the symfony-behat extension https://github.com/Behat/Sy...
But at the end, the process is the same, you have to fetch the "EntityManager", call persist on the user's object, and finally call "$em->flush()"

Cheers!

Reply
Default user avatar

ok here I get this error:
┌─ @BeforeSuite # FeatureContext::bootstrapSymfony()

╳ There is no extension able to load the configuration for "swiftmailer" (in app/config/config_test.yml). Looked for namespace "swiftmailer", found "framework", "security", "twig", "monolog", "doctrine", "sensio_framework_extra", "knp_markdown", "doctrine_cache", "doctrine_migrations", "debug", "web_profiler", "sensio_distribution", "doctrine_fixtures" (Symfony\Component\DependencyInjection\Exception\InvalidArgumentException)

after adding:

/**
* @BeforeSuite
*/
public static function bootstrapSymfony(){
require_once __DIR__.'/../../app/autoload.php';
require_once __DIR__.'/../../app/AppKernel.php';
$kernel = new AppKernel('test', true);
$kernel->boot();
self::$container = $kernel->getContainer();
}

$user = new \AppBundle\Entity\User();
$user->setUsername($username);
$user->setPlainPassword($password);
$user->setRoles(array('ROLE_ADMIN'));

$em = self::$container->get('doctrine')->getManager();
$em->persist($user);
$em->flush();

I think something with new AppKernel ?

Reply

Hey Daka,

Hm, looks like Swiftmailer bundle is not enabled for test environment. Do you enable it for all environment or only for test in AppKernel.php?

Cheers!

Reply
Default user avatar
Default user avatar Stefano Giraldo | posted 5 years ago | edited

Hi, very interesting tutorial!
However I have a question:

Why not to use KernelAwareContext offered by Behat Symfony2Extension?

Implementing then


public function setKernel(KernelInterface $kernel)
    {
    $this->kernel = $kernel;
}

you have the Symfony Kernel available along the Context Class.

Thanx

Reply

Hey Stefano,

Nice catch! Well, it's just due to teaching purposes. It's one of the possible ways to bootstrap Symfony kernel, actually, we show what exactly Symfony2Extension do behind the scene. But you're right, your way is simpler. And we show the example you mentioned in the next chapter, see https://knpuniversity.com/s... - but we use KernelDictionary trait instead, which is even more simple than implementing KernelAwareContext ;)

Cheers!

1 Reply
Default user avatar
Default user avatar Stefano Giraldo | Victor | posted 5 years ago

Thank you Victor, KernelDictionary is interesting too!

Reply
Default user avatar
Default user avatar ShaunDychko | posted 5 years ago

In order for the User class to be recognized I had to use "$user = new \AppBundle\Entity\User();"

Reply

Hey Shaun!

Actually, what you have will *definitely* work (as you know). What you don't see in my example is that (thanks to PhpStorm) as soon as I auto-complete the User class, it added a "use AppBundle\Entity\User" to the top of the class. So if you have that "use" statement, you can have the short version like mine. But, both work :).

Cheers!

Reply
Default user avatar

Fatal error: Class 'AppBundle\Entity\User'

This is the error i receive although i used "use AppBundle\Entity\User; " at the top. Any idea?

Reply

Hey Victor,

It's not entirely clear what error, I suppose you trimmed it in the most interesting place :) Is it "Fatal error: Class 'AppBundle\Entity\User' not found"? Or is it a different error like you have invalid syntax/missing semi-colon/misprint in that class. So it depends. Please, double check that AppBundle\Entity\User class doesn't have syntax problem - PhpStorm could help with it. Also, does that class have "namespace AppBundle\Entity;"? Is class name "User"? Does this class have src/AppBundle/Entity/User.php path in your file system? Also look closer at letter case.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses a very old version of Symfony. The fundamentals of Behat are still valid, but integration with Symfony will be different.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.4.0, <7.3.0",
        "symfony/symfony": "^2.7", // v2.7.4
        "twig/twig": "^1.22", // v1.22.1
        "sensio/framework-extra-bundle": "^3.0", // v3.0.16
        "doctrine/doctrine-bundle": "^1.5", // v1.5.1
        "doctrine/orm": "^2.5", // v2.5.1
        "doctrine/doctrine-fixtures-bundle": "^2.2", // v2.2.1
        "behat/symfony2-extension": "^2.0" // v2.0.0
    },
    "require-dev": {
        "behat/mink-extension": "^2.0", // v2.0.1
        "behat/mink-goutte-driver": "^1.1", // v1.1.0
        "behat/mink-selenium2-driver": "^1.2", // v1.2.0
        "phpunit/phpunit": "^4.8" // 4.8.18
    }
}
userVoice