I'm trying to write some functional tests for my application, the problem I'm having is that unless I actually pull a user out of the database I can't get it to use the new user object. I'd like to reduce the amount of database queries as much as possible.
All my users are stored in a database and accessed through a Doctrine layer. Here is how I have the security setup and what currently works:
// app/security.yml
security:
providers:
administrators:
entity: { class: User, property: email }
encoders:
User:
algorithm: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
firewalls:
dev:
pattern: ^/(_(profiler|wdt|error)|css|images|js)/
security: false
default:
anonymous: ~
http_basic: ~
form_login:
login_path: login
check_path: login-check
csrf_provider: security.csrf.token_manager
logout:
path: /logout
target: login
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: ROLE_ADMIN }
By following http://symfony.com/doc/current/cookbook/testing/simulating_authentication.html I have the following working
$client = static::createClient();
$user = $client->getContainer()->get('doctrine')->getEntityManager()->getRepository('User')->findOneByEmail('nick@example.com');
$session = $client->getContainer()->get('session');
$firewall = 'default';
$token = new UsernamePasswordToken($user, null, $firewall, $user->getRoles());
$session->set('_security_'.$firewall, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$client->getCookieJar()->set($cookie);
$crawler = $client->request('GET', '/some-secure-url');
$this->assertTrue($client->getResponse()->isSuccessful());
What I'd like to be able to do is:
$client = static::createClient();
$user = new User();
$user->setRoles(['ROLE_ADMIN']);
$session = $client->getContainer()->get('session');
$firewall = 'default';
$token = new UsernamePasswordToken($user, null, $firewall, $user->getRoles());
$session->set('_security_'.$firewall, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$client->getCookieJar()->set($cookie);
$crawler = $client->request('GET', '/some-secure-url');
$this->assertTrue($client->getResponse()->isSuccessful());
But currently when I try this approach I get the following
You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.
Any suggestions around this would be brilliant :)
This is because the user provider will try to refresh the user every time with UserProviderInterface::refreshUser(), even though it is available on the session.
This is the default behaviour, as user details might have changed since they were last read from the database. Changing this behaviour would require implementing a custom user provider at this point (see https://github.com/symfony/symfony/issues/1338).
The easiest fix is to add an identifier to your user. If I read your config you've used an email as an identifier:
$user = new User();
$user->setRoles(['ROLE_ADMIN']);
$user->setEmail('foo@bar.com');
Symfony will make a query to the database, but it would also make a query in the real scenario. If you want to change this behaviour, you'll need to provide your own implementation of the UserProviderInterface, which would not make a query on every request.
Alternatively, use another authentication method (like http basic) with the functional tests.