在功能测试中在没有数据库调用的情况下模拟Symfony 2 / Doctrine中的身份验证

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.