I'm using Behat in Symfony2 / Doctrine2. Now, I have this scenario that boils down to the fact that "if I'm logged in and I go to /login, I shoud go to / instead":
@login
Scenario: Go to the login page while being logged in
Given I am logged in
When I go to "/login"
Then I should be on "/"
For the @login, I created the following:
/**
* @BeforeScenario @login
*/
public function loginUser()
{
$doctrine = $this->getContainer()->get('doctrine');
$userRepository = $doctrine->getRepository('MyTestBundle:User');
$user = $userRepository->find(1); // 1 = id
$token = new UsernamePasswordToken($user, NULL, 'main', $user->getRoles());
$this->getContainer()->get('security.context')->setToken($token);
}
In the "when I go to /login" code (the controller gets called), the token seems gone (not what I intended):
/**
* @Route("/login", name="login")
*/
public function loginAction()
{
$token = $this->get('security.context')->getToken();
$fd = fopen('/tmp/debug.log', 'a');
fwrite($fd, $token);
// prints 'AnonymousToken(user="anon.", authenticated=true, roles="")'
...
But in the FeatureContext, it seems to stick around (the way I hoped it would work). In the "Given I am logged in":
/**
* @Given /^I am logged in$/
*/
public function iAmLoggedIn()
{
$token = $this->getContainer()->get('security.context')->getToken();
$fd = fopen('/tmp/debug.log', 'a');
fwrite($fd, $token);
// prints 'UsernamePasswordToken(user="admin", authenticated=true, roles="ROLE_ADMIN")'
...
I run behat like this:
app/console -e=test behat
I also did this in the controller to be sure it's test:
fwrite($fd, $this->get('kernel')->getEnvironment());
// prints 'test'
Any clue how to authenticate a user? I will have to test a lot of admin pages, so it would be nice if I could hook the login into @BeforeSuite, @BeforeFeature (or @BeforeScenario ...) so that I don't get blocked.
(Suggestions on disabling the authentication mechanism for testing, or a way to stub/mock a user are also welcome.)
Oh my. It doesn't work because the DIC inside your FeatureContext isn't shared with your app - your app has separate kernel and DIC. You can get it through Mink. Or, you can simply do it right way :-)
Right way means, that every part of behavior, that is observable by the enduser, should be described inside *.feature, not inside FeatureContext. It means, that if you want to login a user, you should simply describe it with steps (like: "i am on /login", "and i fill in username ...", "i fill in password" and stuf). If you want to do it in multiple times - you should create a metastep.
Metasteps are simply steps, that describe multiple other steps, for example - "i am logged in as everzet". You could read bout them here: http://docs.behat.org/guides/2.definitions.html#step-execution-chaining
Here is an solution for login with OAuth I've used. After number of times of searching for the answer and landing on this page I thought it would be great to share the solution. Hopefully it will help someone.
Background: Symfony2 App using HWIOAuthBundle, hooked up to some OAuth2 provider.
Problem: How do I implement Given I'm logged in
when Behat context in not shared with Symfony context?
Solution:
HWIOAuthBundle uses @buzz
service for all API calls to OAuth providers. So all you need to do is replace Buzz client with your implementation which doesn't call external services, but returns the result straight away. This is my implementation:
<?php
namespace Acme\ExampleBundle\Mocks;
use Buzz\Client\ClientInterface;
use Buzz\Message\MessageInterface;
use Buzz\Message\RequestInterface;
class HttpClientMock implements ClientInterface
{
public function setVerifyPeer()
{
return $this;
}
public function setTimeout()
{
return $this;
}
public function setMaxRedirects()
{
return $this;
}
public function setIgnoreErrors()
{
return $this;
}
public function send(RequestInterface $request, MessageInterface $response)
{
if(preg_match('/\/oauth2\/token/', $request->getResource()))
{
$response->setContent(json_encode([
'access_token' => 'valid',
'token_type' => 'bearer',
'expires_in' => 3600
]));
}
elseif(preg_match('/\/oauth2\/me/', $request->getResource()))
{
$response->setContent(json_encode([
'id' => 1,
'username' => 'doctor',
'realname' => 'Doctor Who'
]));
}
else throw new \Exception('This Mock object doesn\'t support this resource');
}
}
Next step is to hijack the class used by HWIOAuthBundle/Buzz and replace it with the implementation above. We need to do it only for test environment.
# app/config/config_test.yml
imports:
- { resource: config_dev.yml }
parameters:
buzz.client.class: Acme\ExampleBundle\Mocks\HttpClientMock
And finally, you need to set require_previous_session
to false for test environment - therefore I suggest to pass it as parameter.
# app/config/security.yml
security:
firewalls:
secured_area:
oauth:
require_previous_session: false
Now you can implement your step like this.
Specification:
Feature: Access restricted resource
Scenario: Access restricted resource
Given I'm logged in
When I go to "/secured-area"
Then I should be on "/secured-area"
And the response status code should be 200
Implementation:
<?php
/**
* @Given /^I\'m logged in$/
*/
public function iMLoggedIn()
{
$this->getSession()->visit($this->locatePath('/login/check-yourOauthProvider?code=validCode'));
}
The code you're passing is not relevant, anything you pass will be OK as it's not being checked. You can customise this behaviour in HttpClientMock::send
method.
http://robinvdvleuten.nl/blog/handle-authenticated-users-in-behat-mink/ is simple, clean article on how to create a login session and set the Mink session cookie so that the Mink session is logged in. This is much better than using the login form every time to login a user.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With