If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
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.
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.
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.
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!
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?
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)
Think thats a bit too many steps to follow. use this extension to make your lives easier http://inevitabletech.uk/bl...
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
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!
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 ?
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!
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
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!
In order for the User class to be recognized I had to use "$user = new \AppBundle\Entity\User();"
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!
Fatal error: Class 'AppBundle\Entity\User'
This is the error i receive although i used "use AppBundle\Entity\User; " at the top. Any idea?
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!
// 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
}
}
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/