Intro: Custom user implementation to be able to use and Wordpress users:
In our project, we have implemented a custom user provider (for Wordpress users - implements UserProviderInterface) with corresponding custom user (WordpressUser implements UserInterface, EquatableInterface). I have setup a firewall in the security.yml and implemented several voters.
# app/config/security.yml
security:
providers:
wordpress:
id: my_wordpress_user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
default:
anonymous: ~
http_basic: ~
form_login:
login_path: /account
Functional phpunit testing:
So far so good - but now the tricky part: mocking authenticated (Wordpress) users in functional phpunit tests. I have succeeded mocking the WordpressUserProvider so a mocked WordpressUser will be returned on loadUserByUsername(..). In our BaseTestCase (extends WebTestCase) the mocked WordpressUser gets authenticated and the token is stored to session.
//in: class BaseTestCase extends WebTestCase
/**
* Login Wordpress user
* @param WordpressUser $wpUser
*/
private function _logIn(WordpressUser $wpUser)
{
$session = self::get('session');
$firewall = 'default';
$token = new UsernamePasswordToken($wpUser, $wpUser->getPassword(), $firewall, $wpUser->getRoles());
$session->set('_security_' . $firewall, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
self::$_client->getCookieJar()->set($cookie);
}
The problem: losing session data on new request:
The simple tests succeed on the authentication part. Until tests with a redirect. The user is only authenticated one request, and 'forgotten' after a redirect. This is because the Symfony2 test client will shutdown() and boot() the kernel on each request, and in this way, the session gets lost.
Workarounds/solutions:
In a solution provided in question 12680675 only user ID should be used for the UsernamePasswordToken(..) to solve this. Our project needs the full user object.
In the solution provided in Unable to simulate HTTP authentication in functional test the basic HTTP authentication is used. In this case the full user object - including roles - cannot be used.
As suggested by Isolation of tests in Symfony2 you can persist instances by overriding the doRequest() method in the test client. As suggested I have created a custom test client and made an override on the doRequest() method.
Custom test client to 'store' session data between requests:
namespace NS\MyBundle\Tests;
use Symfony\Bundle\FrameworkBundle\Client as BaseClient;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Class Client
* Overrides and extends the default Test Client
* @package NS\MyBundle\Tests
*/
class Client extends BaseClient
{
static protected $session;
protected $requested = false;
/**
* {@inheritdoc}
*
* @param Request $request A Request instance
*
* @return Response A Response instance
*/
protected function doRequest($request)
{
if ($this->requested) {
$this->kernel->shutdown();
$this->kernel->boot();
}
$this->injectSession();
$this->requested = true;
return $this->kernel->handle($request);
}
/**
* Inject existing session for request
*/
protected function injectSession()
{
if (null === self::$session) {
self::$session = $this->getContainer()->get('session');
} else {
$this->getContainer()->set('session', self::$session);
}
}
}
Without the if statement holding the shutdown() and boot() calls, this method is working more or less. There are some weird problems where $_SERVER index keys cannot be found so I would like to properly re-instantiate the kernel container for other aspects of the system. While keeping the if statement, users cannot be authenticated, though the session data is the same before and during/after the request (checked by var_export to log).
Question(s):
What am I missing in this this approach that causes the authentication to fail? Is the authentication (and session check) done directly on/after kernel boot() or am I missing something else? Does anyone has another/better solution to keep the session intact so users will be authenticated in functional tests? Thank you in advance for your answer.
--EDIT--
In addition: the session storage for the test environment is set to session.storage.mock_file. In this way, the session should already be persisted between requests as describe by Symfony2 components here. When checked in the test after a (second) request, the session seems to be intact (but somehow ignored by the authentication layer?).
# app/config/config_test.yml
# ..
framework:
test: ~
session:
storage_id: session.storage.mock_file
profiler:
collect: false
web_profiler:
toolbar: false
intercept_redirects: false
# ..