FOSUserBundle - PHPUnit - Mock a user

2019-01-28 08:28发布

I am using Symfony with the FOSUserBundle and now I like to test some things like:

  • Doctrine lifecycle
  • Controller behind firewall

For those tests I need to be a specific user or at least in a user group. How do I mock a user session so that ...

  • The lifecycle field like "createdAt" will use the logged in user
  • The Controller act like some mocked user is logged in

Example:

class FooTest extends ... {
    function setUp() {
        $user = $this->getMock('User', ['getId', 'getName']);

        $someWhereGlobal->user = $user;

        // after this you should be logged in as a mocked user
        // all operations should run using this user.
    }
}

3条回答
家丑人穷心不美
2楼-- · 2019-01-28 08:44

You can easily do that with LiipFunctionalTestBundle which authorize you lot of shortcut for create Unit Test.

If already you have a form user for create or edit you can use this for your test unit workflow user in your application :

use the makeClient method for logging test

$credentials = array(
    'username' => 'a valid username',
    'password' => 'a valid password'
);

$client = static::makeClient($credentials);

use your form for test your creation

$crawler = $client->request('GET', '/profile');

$form = $crawler->selectButton('adding')->form();
$form['fos_user_profile_form[firstName]'] = 'Toto';
$form['fos_user_profile_form[lastName]'] = 'Tata';
$form['fos_user_profile_form[username]'] = 'dfgdgdgdgf';
$form['fos_user_profile_form[email]'] = 'testfgdf@grgreger.fr';
$form['fos_user_profile_form[current_password]'] = 'gfgfgdgpk5dfgddf';

testing "createdAt" with just call findOneBy in repository user like this

$user = $this->getObjectManager()
             ->getRepository('AcmeSecurityBundle:User')
             ->findOneBy(array('username' => 'testCreateUserUsername'));

$this->assertTrue($user->getCreatedAt() == now());
查看更多
狗以群分
3楼-- · 2019-01-28 08:47

You can do this with LiipFunctionalTestBundle. Once you have installed and configured the Bundle, creating and user and log in in tests is easy.

Create a fixture for your user

This creates a user which will be loaded during tests:

<?php
// Filename: DataFixtures/ORM/LoadUserData.php

namespace Acme\MyBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\MyBundle\Entity\User;

class LoadUserData extends AbstractFixture implements FixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $user = new User();
        $user
            ->setId(1)
            ->setName('foo bar')
            ->setEmail('foo@bar.com')
            ->setPassword('12341234')
            ->setAlgorithm('plaintext')
            ->setEnabled(true)
            ->setConfirmationToken(null)
        ;
        $manager->persist($user);
        $manager->flush();

        // Create a reference for this user.
        $this->addReference('user', $user);
    }
}

If you want to use groups of users, you can see the official documentation.

Log in as this user in your test

As explained in LiipFunctionalTestBundle's documentation, here is how to load the user in the database and log in as this user:

/**
 * Log in as the user defined in the Data Fixture.
 */
public function testWithUserLoggedIn()
{
    $fixtures = $this->loadFixtures(array(
        'Acme\MyBundle\DataFixtures\ORM\LoadUserData',
    ));

    $repository = $fixtures->getReferenceRepository();

    // Get the user from its reference.
    $user = $repository->getReference('user')

    // You can perform operations on this user.
    // ...

    // And perform functional tests:

    // Create a new Client which will be logged in.
    $this->loginAs($user, 'YOUR_FIREWALL_NAME');
    $this->client = static::makeClient();

    // The user is logged in: do whatever you want.
    $path = '/';
    $crawler = $this->client->request('GET', $path);
}
查看更多
在下西门庆
4楼-- · 2019-01-28 08:49

What I would do in this case is to create a CustomWebTestCase which extends the Symfony WebTestCase. In the class I would create a method which does the authentication for me.

Here is an example code:

namespace Company\MyBundle\Classes;

use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\User\User;

abstract class CustomWebTestCase extends WebTestCase
{

    /**
     * @param array|null $roles
     * @return \Symfony\Bundle\FrameworkBundle\Client
     */
    protected static function createAuthenticatedClient(array $roles = null) {
        // Assign default user roles if no roles have been passed.
        if($roles == null) {
            $role = new Role('ROLE_SUPER_ADMIN');
            $roles = array($role);
        } else {
            $tmpRoles = array();
            foreach($roles as $role)
            {
                $role = new Role($role, $role);
                $tmpRoles[] = $role;
            }
            $roles = $tmpRoles;
        }

        $user = new User('test_super_admin', 'passwd', $roles);

        return self::createAuthentication(static::createClient(), $user);
    }

    private static function createAuthentication(Client $client, User $user) {
        // Read below regarding config_test.yml!
        $session = $client->getContainer()->get('session');

        // Authenticate
        $firewall = 'user_area'; // This  MUST MATCH the name in your security.firewalls.->user_area<-
        $token = new UsernamePasswordToken($user, null, $firewall, $user->getRoles());
        $session->set('_security_'.$firewall, serialize($token));
        $session->save();

        // Save authentication
        $cookie = new Cookie($session->getName(), $session->getId());
        $client->getCookieJar()->set($cookie);

        return $client;
    }
}

The code above will directly create a valid user session and will skip the firewall entirely. Therefore you can create whatever $user you want and it will still be valid. The important part of the code is located in the method createAuthentication. This is what does the authentication magic.

One more thing worth mentioning - make sure you have set framework.session.storage_id to session.storage.mock_file in your config_test.yml so that Symfony will automatically mock sessions instead of you having to deal with that in each test case:

framework:
    session:
        storage_id: session.storage.mock_file

Now in your test case you would simply extend MyWebTestCase and call the createAuthenticatedClient() method:

class MyTest extends CustomWebTestCase {
    public function testSomething() {
        //Create authoried and unauthorized clients.
        $authenticatedClient = self::createAuthenticatedClient(array("ROLE_SUPER_ADMIN"));
        $unauthorizedClient = self::createAuthenticatedClient(array("ROLE_INSUFFICIENT_PERMISSIONS"));

        // Check if the page behaves properly when the user doesn't have necessary role(s).
        $unauthorizedClient->request('GET', '/secured-page');
        $response = $unauthorizedClient->getResponse();
        $this->assertFalse($response->isSuccessful());
        $this->assertEquals(403, $response->getStatusCode(), "This request should have failed!");

        // Check if the page behaves properly when the user HAS the necessary role(s)
        $authenticatedClient->request('GET', '/secured-page');
        $response = $authenticatedClient->getResponse();
        $this->assertTrue($response->isSuccessful());
        $this->assertEquals(200, $response->getStatusCode(), "This request should be working!");
    }
}

You can see an example in the Symfony official documentation as well.

查看更多
登录 后发表回答