Behat / Symfony2Extension

Symfony2 extension for Behat
MIT License
394 stars 106 forks source link

Empty security token storage between features #115

Open gcavana opened 7 years ago

gcavana commented 7 years ago

Hi everyone,

I'm posting this issue from stackoverflow as i've got the same problem and no one seems do understand why this is happening. Maybe you can give us more insights on this one.

Stack overflow issue

In our Symfony applications we have custom code that handles authentication. It's based on top of thephpleague/oauth2-server.

We have 2 types of authentication, both of which end up calling TokenStorage::setToken().

All this works perfectly.

Now we decided to add some Behat tests to our bundle:

Scenario: I am able to login with type 1
    When I login with type 1
    Then I am successfully logged in as a "type1" member

Scenario: I am able to login with type 2
    When I login with type 2
    Then I am successfully logged in as a "type2" member
(I changed the actual wording of the scenarios in order not to expose business-specific terms)

In order to test the Then steps, I need access to the TokenStorage in my Contexts because I basically want to test if the user has the correct security role:

default:
    gherkin:
        cache: null
    suites:
        mybundle:
            paths: [ %paths.base%/src/My/Bundle/Features ]
            contexts:
                - My\Bundle\Context\LoginContext
                    tokenStorage: '@security.token_storage'
    extensions:
        Behat\Symfony2Extension:
            kernel:
                env: "test"
                debug: "true"

My context looks like this:

use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Behat\Behat\Context\Context;

class LoginContext implements Context
{
    private $tokenStorage;

    public function __construct(TokenStorage $tokenStorage)
    {
        $this->tokenStorage = $tokenStorage;
    }

    /**
     * @Then I am successfully logged in as a :expectedRole member
     */
    public function iAmSuccessfullyLoggedInAsAMember($expectedRole)
    {
        $found = false;
        foreach ($this->tokenStorage->getToken()->getRoles() as $role) {
            if ($role->getRole() == $expectedRole) {
                $found = true;
                break;
            }
        }

        if (! $found) {
            throw new \Exception('Incorrect role');
        }
    }
}

What happens is:

if I run only the first scenario, it works OK if I run only the second scenario, it works OK if I run both of them, then on the 2nd scenario I get the error Call to a member function getRoles() on a non-object Why does this happen ? How can I make it work correctly ?

My versions are:

behat/behat: 3.1.0
behat/gherkin: 4.4.1
behat/symfony2-extension: 2.1.1
symfony/symfony: 2.6.13
One solution I tried is to have my context implement the Behat\Symfony2Extension\Context\KernelAwareContext interface instead and then I did this:
use Behat\Symfony2Extension\Context\KernelAwareContext;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;

class LoginContext implements KernelAwareContext
{
    private $tokenStorage;
    private $kernel;

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

    /**
     * @BeforeScenario
     */
    public function getDependencies(BeforeScenarioScope $scope)
    {
        $container = $this->kernel->getContainer();

        $this->tokenStorage = $container->get('security.token_storage');
    }
}

My thinking was that, by explicitly retrieving TokenStorage before each scenario, I would get a fresh instance each time and therefore it would work.

However, it behaved exactly the same :( .

What am I missing?

sroze commented 7 years ago

Hey @gcavana. That's an interesting question, my guess is that the first step produces some side-effects that are conflicting with the 2nd request. That is why we often have to restart the Symfony kernel across requests (it's the default in Symfony's FrameworkBundle Client).

I can't tell you what's wrong with that small amount of details, but 2 things: ensure that your kernel is restarted between your scenarios. If not, using var_dumps or any debugger, have a look if anything is calling setToken with a null value on your SecurityStorage (with an xdebug_print_function_stack() function call if you'll see who is calling it easily). If all of that do not give you the answer, could you put somewhere a bit of code providing this problem and I can have a look.

gcavana commented 7 years ago

Thanks for the quick reply @sroze ! I'm gonna use xdebug to find the caller and get back to you if i found something that can help.