First of all I've read this related question and this other approach (blog post).
This is my behat.yml
:
default:
suites:
users:
contexts:
- DoctrineFixturesContext
- FeatureContext
- Behat\MinkExtension\Context\MinkContext
- Sanpi\Behatch\Context\RestContext
- Sanpi\Behatch\Context\JsonContext
paths: [%paths.base%/features/users]
extensions:
Behat\Symfony2Extension:
kernel:
env: "test"
debug: "true"
Behat\MinkExtension:
base_url: ''
sessions:
default:
symfony2: ~
Sanpi\Behatch\Extension: ~
Now what I want is to reproduce the behavior of a user, which is:
Authorization
header with the tokenThe easiest way I've found is to simply mimic the user behavior:
class FeatureContext implements Context, SnippetAcceptingContext
{
private $request;
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* @BeforeScenario @Login
*/
public function login()
{
$d = $this->request->send('POST', '/login_check', ['_username' => 'test', '_password' => 'test']);
$data = json_decode($d->getContent());
$this->request->setHttpHeader('Authorization', 'Bearer '.$data->token);
}
}
The user is previously loaded through fixtures. This works well except that the Authorization
header is not kept through the next steps:
Feature: Get my availables menus
@Login
Scenario: Get my menus
When I send a "GET" request to "/api/menus"
Then the response status code should be 200
The response is 401.
Why is the Authorization header not kept through the scenario? How may I achieve this simple task?
As you can read here, following here, the Behatch/RestContext is quite useful (a lot of shortcuts and handful methods) but it's quite messy with the symfony2 integration.
As @stof says in this comment:
The rest-context is a huge mistake altogether. Mink is not a REST client (browsers are not REST clients either). There is no point using the Mink driver to perform the REST API calls. It is the wrong tool for the job.
Longo story short: it seems that the request headers you set are not passed with the request.
Having said that, you can try with this workaround:
Feature: Get my availables menus
@Login
Scenario: Get my menus
# note the change in the following line
# When I send a "GET" request to "/api/menus"
When I am on "/api/menus"
Then the response status code should be 200
Basically, using the When I am on... you are using the MinkContext and the request headers are properly passed with the request.
Of course I know this is not the perfect solution (eg: how to handle POST request??) but at least the specific test you described should pass.
Let me know. Ciao
Why is the Authorization header not kept through the scenario? How may I achieve this simple task?
I would first of all what is this Request
object that you're injecting in your context? If your idea is to inject the request object that will be used in your Symfony application, then that won't work as the object will be created by the front controller when Mink will "visit the page".
Moreover, I would recommend to use directly the Symfony's kernel in your context files to send requests to your application. With the Symfony2Extension of Behat, you can instanciate your FeatureContext
with the kernel by mentioning the service you want to inject in the behat.yml
file:
default:
suites:
users:
contexts:
- FeatureContext:
- @kernel
Then, you can create request objects manually and send them to the Symfony kernel. Here's an example of sending the almost the same POST
request than in your example:
class FeatureContext implements Context, SnippetAcceptingContext
{
private $kernel;
public function __construct(KernelInterface $kernel)
{
$this->kernel = $kernel;
}
/**
* @What user :username is authenticating itself with :password
*/
public function userIsAuthenticatingItselfWith($username, $password)
{
$response = $this->kernel->handle(Request::create(
'/login_check',
'POST',
[],
[],
[],
[
'CONTENT_TYPE' => 'application/x-www-form-urlencoded'
],
http_build_query([
'_username' => $username,
'_password' => $password,
])
));
// Do whatever you want with the `$response` object to check that
// the user is successfully authenticated for instance..
}
}
But you can see that I changed the method signature and actually didn't store the token or anything else: this is intended to actually test this authentication, but not as a Given step.
The Symfony authentication system contains a TokenStorage
object that stores the current authentication token. So if you want to "fake" an authentication, you can simply set a token in the object, with a step definition that will probably look like that:
class SecurityContext implements Context
{
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
* @param TokenStorageInterface $tokenStorage
*/
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
/**
* @Given I am authenticated as :username
*/
public function iAmAuthenticated()
{
$token = new YourTokenClass(['ROLE_USER']);
$token->setUser(new YouUserClass($username));
$this->tokenStorage->setToken($token);
}
}
And then, instead of scenario hooks, you can simply use the Given I am authenticated as :username
in your scenario or even with the Background
s.
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