Reputation: 27330
In Symfony 3 with Doctrine, I am trying to have a "last updated by" column in the database, which is automatically set to the logged in user's username whenever a change is saved to the table.
I am using an EventSubscriber
to do this, which works when I use the system for real, but when I run the unit tests it thinks there is no user logged in.
I am following the instructions on how to implement authentication for unit tests which advises two methods - either using Basic auth or setting up a token. Both these methods allow the tests to run as if the user is authenticated, but neither of them allow me to retrieve the username of the logged in user, so I can't test that the "last updated by" column is correctly being set.
What am I doing wrong? Is there some other way I should be retrieving the username of the logged-in user in the EventSubscriber
?
This is my EventSubscriber
implementation which works for a real requests, but returns a null token when the tests are running:
class BaseEntitySubscriber implements EventSubscriber {
public function getSubscribedEvents() {
return array('prePersist');
}
public function prePersist(LifecycleEventArgs $args) {
$em = $args->getEntityManager();
$token = $this->ts->getToken(); // returns null during tests
$username = $token ? $token->getUsername() : null;
if ($username === null) throw new \Exception("User is not logged in!");
$entity = $args->getEntity();
$entity->setLastUpdateBy($username);
}
}
# services.yml
AppBundle\EventListener\BaseEntitySubscriber:
tags:
- { name: doctrine.event_subscriber, connection: default }
arguments: [ "@security.token_storage" ]
EDIT: @dbrumann the test code looks like this: (roughly)
public function testEditLookup()
{
$idLookup = $this->getFixtureId('lookup.example');
// Get the existing one first so we know if it changes later.
$crawler = $this->client->request('GET', "/lookup/$idLookup");
$lookup = $this->decodeSymfonyJSON($this->client->getResponse());
$this->assertEquals('Old title', $lookup['title']);
$this->assertEquals('system', $lookup['last_update_by']);
$this->assertEquals('Example', $lookup['detail']);
// Make the change.
$crawler = $this->client->request('PATCH',
"/lookup/$idLookup",
array('title' => 'New title')
);
$this->checkSymfonyError($this->client->getResponse());
// Retrieve it again to confirm it is now different.
$crawler = $this->client->request('GET', "/lookup/$idLookup");
$lookup = $this->decodeSymfonyJSON($this->client->getResponse());
$this->assertEquals('New title', $lookup['title']);
$this->assertEquals('testuser', $lookup['last_update_by']);
// Make sure this hasn't changed.
$this->assertEquals('Example', $lookup['detail']);
}
Upvotes: 0
Views: 155
Reputation: 27330
I think I have worked this one out. It turns out that - assuming I am retrieving the user in the correct manner - you need to set a token yourself:
$user = 'testuser';
$pass = 'testpass';
$this->client = static::createClient(array(), array(
'PHP_AUTH_USER' => $user,
'PHP_AUTH_PW' => $pass,
'HTTPS' => true, // Default to HTTPS
));
// Also set a token so the username can be retrieved later
$firewallContext = 'main';
$token = new UsernamePasswordToken($user, $pass, $firewallContext, array('ROLE_ADMIN'));
$ts = $this->client->getContainer()->get('security.token_storage');
$ts->setToken($token);
This is in the test case's setUp()
implementation.
EDIT: Upon further testing there is one more thing to be aware of. The UsernamePasswordToken
is used when loading fixtures, and the PHP_AUTH_USER
is used when running tests. This means you can populate your initial data as one user, then have a different user run the tests. This is handy to ensure any 'last updated by' fields are changed to the correct username.
If you are using a Doctrine event subscriber as I am, then the prePersist
hook will see the UsernamePasswordToken
user, while the preUpdate
hook will see the PHP_AUTH_USER
user.
Upvotes: 1