Malvineous
Malvineous

Reputation: 27330

Cannot get username during Symfony unit tests

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

Answers (1)

Malvineous
Malvineous

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

Related Questions