Posts tagged 'symfony'

Accessing Symfony private services in Behat

published on February 01, 2019.

Since Symfony 3.4 the services in the service container are private by default. While this decision made us write better production code by making us use Dependency Injection more and rely on the service container less, using these services in a test environment proved to be a challenge.

Since Symfony 4.1 there’s a special service container in the test environment which allows fetching private services in tests.

In a Behat test this test service container is not available through the static::$container property as it is in a WebTestCase or a KernelTestCase, but it is available under the test.service_container name in the service container.

We need Symfony, Behat, and Behat Symfony2 extension with the Behat Symfony2 extension configured to bootstrap an instance of the App\Kernel for us:

./behat.yml

default:
    extensions:
        Behat\Symfony2Extension:
          kernel:
            bootstrap: features/bootstrap/bootstrap.php
            class: App\Kernel

    suites:
      system:
        paths:
          - '%paths.base%/features/system.feature'
        contexts:
          - SystemContext:
              kernel: '@kernel'

If the behat.yml example looks weird, I’m reusing it from my previous blog post on testing Symfony commands with Behat.

Now that we have injected the kernel into our Context file, we can get the service container from the kernel, and from that service container access the test.service_container:

./features/bootstrap/SystemContext.php

<?php

use App\Kernel;
use Behat\Behat\Context\Context;

class SystemContext implements Context
{
    public function __construct(Kernel $kernel)
    {
        $testContainer = $kernel->getContainer()->get('test.service_container');
        $someService = $testContainer->get(App\Some\Service\We\Need::class);
    }

The test service container has all the services public and we can access them without worrying if they are private or public.

If you’re project is still on Symfony 3.4 or Symfony 4.0, Tomas Votruba has a blog post explaining how to achieve something similar using a compiler pass.

Happy hackin’!

Versions used for examples: Symfony 4.1, Behat 3.4, Behat Symfony2 Extension 2.1.
Tags: symfony, behat, private services, testing, php.
Categories: Programming, Development.

Testing Symfony commands with Behat

published on January 18, 2019.

The other day I was creating a Symfony command that will be periodically executed by a cronjob. I decided to write a Behat test for it, to see what a test like that would look like. Plus, just because it is executed by the system from a command line, doesn’t mean we can skimp on the business requirements.

We need Symfony, Behat, and Behat Symfony2 extension. In the behat.yml file we configure the Behat extension to boot up the Kernel for us and pass it in is a constructor argument to our Behat Context:

./behat.yml

default:
    extensions:
        Behat\Symfony2Extension:
          kernel:
            bootstrap: features/bootstrap/bootstrap.php
            class: App\Kernel

    suites:
      system:
        paths:
          - '%paths.base%/features/system.feature'
        contexts:
          - SystemContext:
              kernel: '@kernel'

We enable and configure the Behat Symfony2 extension, and tell Behat that the system suite will use the system.feature feature file and the SystemContext context which takes one constructor argument, the kernel. I don’t like to put everything into the default FeatureContext for Behat, but rather split different contexts into, well, different contexts. That’s why I created the separate SystemContext.

The boostrap.php file is created when installing the extension (at least, it was created for me as I installed it using Symfony Flex):

./features/bootstrap/bootstrap.php

<?php
putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = 'test');
require dirname(__DIR__, 2).'/config/bootstrap.php';

The system.feature file doesn’t have much, just an example scenario:

./features/system.feature

Feature: System executed commands

  Scenario: Behat testing a Symfony command
    Given I am the system
    When I greet the world
    Then I should say "Hello World"

The SystemContext

The SystemContext file is where it gets interesting:

./features/bootstrap/SystemContext.php

<?php

use App\Kernel;
use Behat\Behat\Context\Context;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Webmozart\Assert\Assert;

class SystemContext implements Context
{
    /**
     * @var Application
     */
    private $application;

    /**
     * @var BufferedOutput
     */
    private $output;

    public function __construct(Kernel $kernel)
    {
        $this->application = new Application($kernel);
        $this->output = new BufferedOutput();
    }

It implements the Behat Context interface so that Behat recognizes it as a context.

In the constructor we create a new console Application with the Kernel that the Behat Symfony2 extension created for us. We will use this application instance to run the command that we are testing. For all intents and purposes, this application instance acts the same as the application instance that gets created in the bin/console script that we usually use to run Symfony commands.

We also create a BufferedOutput in the constructor, that will hold the output that the command produces, which we can later on use to assert did the command produce the desired output.

Behat step definitions

The steps are defined like so (it’s in the same SystemContext.php file as the previous example):

./features/bootstrap/SystemContext.php

<?php

class SystemContext implements Context
{
    /**
     * @Given I am the system
     */
    public function iAmTheSystem()
    {
        Assert::same('cli', php_sapi_name());
    }

    /**
     * @When I greet the world
     */
    public function iGreetTheWorld()
    {
        $command = 'app:hello-world';
        $input = new ArgvInput(['behat-test', $command, '--env=test']);
        $this->application->doRun($input, $this->output);
    }

    /**
     * @Then I should say :sentence
     */
    public function iShouldSay($sentence)
    {
        $output = $this->output->fetch();

        Assert::same($sentence, $output);
    }

I am the system

In the first Behat step we assert that the PHP interface is the cli. Not sure how it could be anything else in this case, but let’s have that in there.

I greet the world

The second Behat step is where the fun part happens, where we run the command. The ArgvInput takes an array of parameters from the CLI in the argv format. In the case of bin/console it ends up being populated from $_SERVER['argv']. In this case though, we need to populate it on our own.

The first argument is always the name that was used to run the script and it ends up being just a “placeholder”, hence the behat-test value. We can put in anything there, really.

The second parameter is the command that we want to run: app:hello-world. It is the same string we would use when executing that command through bin/console. Because we created an instance of the Application in the constructor, Symfony will know exactly what command that is.

The third parameter is an option to tell Symfony to run the command in the test environment.

Once we have the input ready, we tell the application to run using the doRun method, passing in the input and the output (which is a BufferedOutput).

I should say :sentence

In the third Behat step we fetch the output and assert that it is the same as the output we expected it to be.

Make it reusable

To make it a bit more reusable, the running of the command in the iGreetTheWorld step can be extracted to a private method so that it all reads a little bit nicer. The final result looks something like this:

./features/bootstrap/SystemContext.php

<?php

use App\Kernel;
use Behat\Behat\Context\Context;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Webmozart\Assert\Assert;

class SystemContext implements Context
{
    /**
     * @var Application
     */
    private $application;

    /**
     * @var BufferedOutput
     */
    private $output;

    public function __construct(Kernel $kernel)
    {
        $this->application = new Application($kernel);
        $this->output = new BufferedOutput();
    }

    /**
     * @Given I am the system
     */
    public function iAmTheSystem()
    {
        Assert::same('cli', php_sapi_name());
    }

    /**
     * @When I greet the world
     */
    public function iGreetTheWorld()
    {
        $this->runCommand('app:hello-world');
    }

    /**
     * @Then I should say :sentence
     */
    public function iShouldSay($sentence)
    {
        $output = $this->output->fetch();

        Assert::same($sentence, $output);
    }

    private function runCommand(string $command)
    {
        $input = new ArgvInput(['behat-test', $command, '--env=test']);
        $this->application->doRun($input, $this->output);
    }

Happy hackin’!

Versions used for examples: Symfony 4.1, Behat 3.4, Behat Symfony2 Extension 2.1.
Tags: symfony, behat, commands, bdd, testing, php.
Categories: Programming, Development.

Read-only Symfony form field

published on April 10, 2017.
Heads-up! You're reading an old post and the information in it is quite probably outdated.

The future me will be grateful for this post. I always get it wrong the first time.

To set a Symfony form field as a read-only, we can’t set the readonly attribute as an option on that field:

src/AppBundle/Form/FooType.php

<?php
declare(strict_types=1);

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class FooType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('title', TextType::class, [
                'readonly' => true,
            ]);
    }
}

This won’t work, and will give the The option "readonly" does not exist. Defined options are:... exception.

We need to set it as an attribute of that field:

src/AppBundle/Form/FooType.php

<?php
declare(strict_types=1);

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class FooType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('title', TextType::class, [
                'attr' => ['readonly' => true],
            ]);
    }
}

Happy hackin’!

Tags: php, symfony, form.
Categories: Programming, Development.

Loading fixtures for a Symfony app in Behat tests

published on March 21, 2017.
Heads-up! You're reading an old post and the information in it is quite probably outdated.

Performing end to end testing of any application requires from us to have a set of reliable test data in the database.

If we write a Symfony application and use Behat to do the end to end testing, the we can use the Doctrine fixtures bundle to create the required fixture loaders and load them in our Behat scenarios when required, using the BeforeScenario hook.

Install Doctrine fixtures bundle

Using composer we can install the Doctrine fixtures bundle:

composer require --dev doctrine/doctrine-fixtures-bundle:2.3.0

and enable the bundle in the AppKernel:

app/AppKernel.php

diff --git a/app/AppKernel.php b/app/AppKernel.php
index 0d22098..c30e863 100644
--- a/app/AppKernel.php
+++ b/app/AppKernel.php
@@ -27,6 +27,7 @@ class AppKernel extends Kernel
             $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
             $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
             $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
+            $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
         }

         return $bundles;

Write a fixture loader

Now we can write a fixture loader. Writing fixture loaders is explained well in the official documentation.

Here’s an example fixture loader for the FOSUser bundle, creating users for the application:

src/AppBundle/DataFixtures/ORM/LoadUserData.php

<?php

namespace AppBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class LoadUserData implements FixtureInterface, ContainerAwareInterface
{
    /**
     * @var ContainerInterface
     */
    private $container;

    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }

    public function load(ObjectManager $manager)
    {
        foreach ($this->getData() as $data) {
            $userManager = $this->container->get('fos_user.user_manager');

            $user = $userManager->createUser();

            $user->setUsername($data['username']);
            $user->setUsernameCanonical($data['username']);
            $user->setPlainPassword($data['password']);

            $user->setEmail($data['email']);
            $user->setEmailCanonical($data['email']);

            $user->addRole($data['role']);
            $user->setEnabled($data['enabled']);

            $userManager->updateUser($user, true);
        }
    }

    private function getData()
    {
        return [
            [
                'username' => 'admin',
                'password' => 'adminpassword',
                'email' => 'admin@email.com',
                'firstname' => 'Boss',
                'lastname' => 'Big',
                'role' => 'ROLE_ADMIN',
                'enabled' => true,
            ],
        ];
    }
}

Do note that the LoadUserData fixture loader also implements the ContainerAwareInterface, meaning that it will get an instance of the ContainertInterface when invoked through the bin/console doctrine:fixtures:load console command.

We use this instance of the container to get the user manager from the FOSUser bundle. In turn, we use the user manager to create and update the users we are loading with this fixture loader.

Rest of it is straightforward — we set the username, email, password, role on the user and update it. The user manager will handle the password encryption as per the application configuration, saving the user to the database, and so on…

Load fixtures in Behat tests

The Behat test file has a bit more to it.

It requires the Behat Symfony2 extension (so far it works for Symfony 3 applications as well!)

composer require --dev behat/symfony2-extension:2.1.1

Next, we need to tell Behat to pass an instance of the AppKernel to our test. We do so through the behat.yml file:

behat.yml

default:
    suites:
        my_feature:
            contexts:
                - 'FeatureContext':
                    kernel: '@kernel'
    extensions:
        Behat\Symfony2Extension:
            kernel:
                class: AppKernel

This will allow our FeatureContext test file to get an instance of a KernelInterface, the AppKernel, through the constructor:

features/bootstrap/FeatureContext.php

<?php

use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;

use AppBundle\DataFixtures\ORM\LoadUserData;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Loader;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;


class FeatureContext
{
    protected $container;

    protected $entityManager;

    public function __construct(KernelInterface $kernel)
    {
        $this->container = $kernel->getContainer();

        $this->entityManager = $this->container->get('doctrine.orm.default_entity_manager');

        $schemaTool = new SchemaTool($this->entityManager);
        $schemaTool->dropDatabase();
        $metadata = $this->entityManager->getMetadataFactory()->getAllMetadata();
        $schemaTool->createSchema($metadata);
    }
}

We get the container from the kernel and the entity manager from the container. No need to setup anything, it’s all taken care of under the hood.

We also use this “occasion” to make sure that the database schema is recreated before the test is ran.

The final step is to load the fixtures data we need in our FeatureContext.php test file:

    /**
     * @BeforeScenario
     */
    public function loadDataFixtures(BeforeScenarioScope $scope)
    {
        $userData = new LoadUserData();
        $userData->setContainer($this->container);

        $loader = new Loader();
        $loader->addFixture($userData);

        $purger = new ORMPurger();
        $purger->setPurgeMode(ORMPurger::PURGE_MODE_DELETE);

        $executor = new ORMExecutor($this->entityManager, $purger);
        $executor->execute($loader->getFixtures());
    }

The main thing to note here is that the container is not automatically set on the LoadUserData fixture loader, so we need to do that manually by calling the setContainer method with $this->container as an argument. Remember, we got the container instance from the kernel in the class constructor.

We tell the loader which fixture to load, the purger to delete any existing records from the database and finally the executor to load our fixtures.

Now when we run the Behat test suite, the database, in the environment against which we run the tests, will have a fresh set of data every time.

Happy hackin’!

Tags: behat, symfony, fixtures, php.
Categories: Programming, Development.
Robert Basic

Robert Basic

Software engineer, consultant, open source contributor.

Let's work together!

If you require outsourcing or consulting help on your projects, I'm available!

Robert Basic © 2008 — 2019
Get the feed