2

I have a problem with my PHPUnit test on Symfony2. To connect to my application, I use a web service, so I created a UserProvider. In my function loadUserByUsername I use Symfony2 parameters saved in app/config/parameters.yml. As I'm not in a controller I need to use the global variable $kernel and get my args like this:

global $kernel;

$url = $kernel->getContainer()-getParameter('myparam');

When I use my application, it works, but when I write my test like this:

$crawler = $client->request('GET', '/login');
$form = $crawler->selectButton('submit')->form();
$form['_username'] = $username;
$form['_password'] = $pass;

and execute PHPUnit I get this error :

Fatal error : Call to a member function getContainer()

How can I access Symfony2 parameters or use getContainer when I execute PHPUnit?

2 Answers 2

2

In the Symfony documentation How to Create a custom UserProvider, under 'Create a Service for the User Provider' it states:

The real implementation of the user provider will probably have some dependencies or configuration options or other services. Add these as arguments in the service definition.

So, rather than using a global $kernel variable you should be passing the relevant parameters into your user provider service by defining them as arguments in the service definition. For example:

services:
    webservice_user_provider:
        class: Acme\WebserviceUserBundle\Security\User\WebserviceUserProvider
        arguments: [%parameter_one%, %parameter_two%, ...]

As with any service, your user provider service class must then have a constructor which takes arguments corresponding to those in the service definition and stores them in private variables for use in the service's methods:

class WebserviceUserProvider implements UserProviderInterface
{
    private parameterOne;
    ...

    public function __construct($parameterOne, ...)
    {
        $this->parameterOne = $parameterOne;
        ...
    }
    ...
}

I've been writing Symfony apps for four years and I've never need to use the global $kernel variable. I daresay there may be valid circumstances but in general I'd say it's to be avoided.

Sign up to request clarification or add additional context in comments.

5 Comments

thank redbirdo, it works ! i pass @service_container to my service and it's passed in PHPUnit
@Remi then you should have accepted my answer, not redbirdo's XD He doesn't mention the @service_container at all. By the way as I replied in a comment to my answer that is not a best practice anyway so you should probably refactor your service a little bit :)
@Remi - it's really better to pass specific parameters / services to your service because it makes the interface / dependencies explicit.
@FrancescoCasula He's a she ;0)
@FrancescoCasula I was well aware that the container can be passed into a service but I didn't mention it because I don't think it should be done.
2

First of all you should not access the kernel by using the global keyword. You can always inject it as a dependency (i.e. @kernel). Still if your goal is just to access the container then you should inject just the service container which is @service_container. The right and best way is to inject just the parameters like redbirdo suggested (i.e. %parameter_one%, %parameter_two%).

Regarding your test you should create and boot a propel kernel in order to be able to use it.

I suggest you create a KernelTestCase class to do that so that your test can extend it and access the container and everything like you do in your controllers.

<?php

namespace AppBundle\Tests;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase as TestCase;

/**
 * Class KernelTestCase
 * @package AppBundle\Tests
 * @author Francesco Casula <[email protected]>
 */
abstract class KernelTestCase extends TestCase
{
    /**
     * @var \Symfony\Component\DependencyInjection\ContainerInterface
     */
    private $container;

    /**
     * {@inheritdoc}
     */
    public function tearDown()
    {
        $this->container = null;
        parent::tearDown();
    }

    /**
     * @param array $options
     * @return \Symfony\Component\DependencyInjection\ContainerInterface
     */
    protected function getContainer(array $options = [])
    {
        if (!$this->container) {
            static::bootKernel($options);
            $this->container = static::$kernel->getContainer();
        }

        return $this->container;
    }

    /**
     * @param string $parameter
     * @return mixed
     */
    protected function getParameter($parameter)
    {
        return $this->getContainer()->getParameter($parameter);
    }
}

And then in your test class...

<?php

namespace AppBundle\Tests\IntegrationTests;

use AppBundle\Tests\KernelTestCase as TestCase;

/**
 * Class ExampleTest
 * @package AppBundle\Tests\IntegrationTests
 * @author Francesco Casula <[email protected]>
 */
class ExampleTest extends TestCase
{
    public function testMyMethod()
    {
        $parameter = $this->getParameter('whatever');

        // ...

        $service = $this->getContainer()->get('service');
    }
}

4 Comments

I don't think this relevant to the OP's issue. The test fragment contains a reference to $client and $crawler which means they must be using the Symfony WebTestCase. WebTestCase boots the kernel for you and you use $this->getClient()->getContainer() to get the container. The question did not ask how to get the container / parameters from the test program but why use of the global $kernel failed when running a test.
I answered saying how to get the kernel (not by global) giving three different ways of doing that. For the sake of completeness I added also a way to get a kernel in a test case. So the answer is relevant also if you provide additional information you know...
Thank for your help it works with the arg @service_container in the service declaration !
I'm glad it helped! Unfortunately that is not a best practice so you should probably refactor your service a little bit. Consider passing to your class just what it is needed :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.