Unit Testing and FOSUserBundle - unit-testing

I'm writing unit tests for my Symfony 2 app which uses FOSUserBundle. Unlike this similar question:
FOSUserBundle Unit testing
however, I don't use HTTP authentication (only a login form) and I need to use actual user entities not fake in-memory ones.
Despite a lot of searching and trying, I simply can't get it to work and the entire process is so intransparent that I don't even know where to begin. Here's the code I have:
protected $em;
protected $client;
protected $testuser;
public function setUp() {
$kernel = static::createKernel();
$kernel->boot();
$this->em = $kernel->getContainer()->get('doctrine.orm.entity_manager');
$this->em->beginTransaction();
$this->client = static::createClient();
$usermanager = $kernel->getContainer()->get('fos_user.user_manager');
$this->testuser = $usermanager->createUser();
$this->testuser->setUsername('test');
$this->testuser->setEmail('test#lemuria.org');
$this->testuser->setPlainPassword('test');
$usermanager->updateUser($this->testuser);
}
public function testLogin() {
$crawler = $this->client->request('GET', '/en/login');
$form = $crawler->selectButton('_submit')->form(array(
'_username' => 'test',
'_password' => 'test',
));
$this->client->submit($form);
$this->assertTrue($this->client->getResponse()->isRedirect(), 'should be redirected');
$this->assertTrue($this->client->getResponse()->isRedirect('http://localhost/en/account'), 'should be redirected to account page');
$crawler = $this->client->followRedirect();
and it fails on the 2nd assertion. As far as I can figure out, it redirects back to the login page.
I'm stuck and I don't even know where to start looking for a solution because it's apparently impossible to simply figure out WHY the login fails.

You could try adding echo $this->client->getResponse()->getContent() to show you the actual response where you could look for errors

Create an AbstractControllerTest and create an authorized client on setUp() as follow:
abstract class AbstractControllerTest extends WebTestCase
{
/**
* #var Client
*/
protected $client = null;
public function setUp()
{
$this->client = $this->createAuthorizedClient();
}
/**
* #return Client
*/
protected function createAuthorizedClient()
{
$client = static::createClient();
$container = $client->getContainer();
$session = $container->get('session');
/** #var $userManager \FOS\UserBundle\Doctrine\UserManager */
$userManager = $container->get('fos_user.user_manager');
/** #var $loginManager \FOS\UserBundle\Security\LoginManager */
$loginManager = $container->get('fos_user.security.login_manager');
$firewallName = $container->getParameter('fos_user.firewall_name');
$user = $userManager->findUserBy(array('username' => 'REPLACE_WITH_YOUR_TEST_USERNAME'));
$loginManager->loginUser($firewallName, $user);
// save the login token into the session and put it in a cookie
$container->get('session')->set('_security_' . $firewallName,
serialize($container->get('security.context')->getToken()));
$container->get('session')->save();
$client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));
return $client;
}
}
NOTE: Please, replace the username with your test username.
Then, extends the AbstractControllerTest and use the global $client to make requests as follow:
class ControllerTest extends AbstractControllerTest
{
public function testIndexAction()
{
$crawler = $this->client->request('GET', '/admin/');
$this->assertEquals(
Response::HTTP_OK,
$this->client->getResponse()->getStatusCode()
);
}
}
This method tested and works fine

Related

how to test slim middleware (phpunit)

I created a Middleware wich should only redirect the user to an other Website (given in Request Url by the Parameter redirect)
class Middleware
{
public function __invoke($request, $response, $next)
{
// Call next middleware or app
$response = $next($request, $response);
$redirectUrl = //get redirect url
return $response->withStatus(200)->withHeader('Location', $redirectUrl);
}
}
I already testet this and the Redirect works fine. So I came to that Point to write Unit-Tests. I failed ... This was my attempt:
class MiddlewareTest extends \PHPUnit_Framework_TestCase
{
public $request = array(...); //inserted needed properties
public function testInvoke(String $url) {
$next = function () : bool
{
return true;
}; //empty function
$request['request']['scriptUri'] = "/parameterStuff&redirect=" . $url; //overwrite the Uri with provided Url
$redirect = new Middleware($request, array(), $next);
//just to test if result of response still empty
$iCount = count((array)$redirect);
$this->assertEquals(0, $iCount);
}
public function invokeProvider() : array
{
return array(
array('http://example.com')
);
}
}
This test is successful but ofc it shouldn't... The return of this function should be a valid response. I tested this in my Browser and echo the return. It has a value there and it's the correct response with the expected Header. The return Value I receive in my Unit-Test is an empty object.
I red the Slim Documentation about the response Object and it sais:
This method returns a copy of the Response object that has the new header value.
So I should definitely receive something from it. I also tried to return a copy of the response:
$copyresponse = response->withStatus(200)->withHeader('Location', $redirectUrl);
return $copyresponse;
This don't works as well. Any Idea what could cause my Problem and how to solve it?
(I want to test if the redirect url is set correctly in the response to ensure that the redirect would work)
You have to mock the request and check if the Location header is correctly set, its length is 1 and the status code is 200. I wrote some different middleware and I used this method.
class LocationTest extends \PHPUnit_Framework_TestCase
{
/**
* PSR7 request object.
*
* #var Psr\Http\Message\RequestInterface
*/
protected $request;
/**
* PSR7 response object.
*
* #var Psr\Http\Message\ResponseInterface
*/
protected $response;
protected $headers;
protected $serverParams;
protected $body;
/**
* Run before each test.
*/
public function setUp()
{
$uri = Uri::createFromString('https://example.com:443/foo/bar');
$this->headers = new Headers();
$this->headers->set('REMOTE_ADDR', '127.0.0.1');
$this->cookies = [];
$env = Environment::mock();
$this->serverParams = $env->all();
$this->body = new Body(fopen('php://temp', 'r+'));
$this->response = new Response();
$this->request = new Request('GET', $uri, $this->headers, $this->cookies, $this->serverParams, $this->body);
}
/**
* #dataProvider locationProvider
*/
public function testLocation($url)
{
$options = array(
'ip' => '192.*',
);
$mw = new RestrictRoute($options);
$next = function ($req, $res) {
return $res;
};
$uri = Uri::createFromString('https://example.com:443/foo/bar?redirect=' . $url);
$this->request = new Request('GET', $uri, $this->headers, $this->cookies, $this->serverParams, $this->body);
$redirect = $mw($this->request, $this->response, $next);
$location = $redirect->getHeader('Location');
$this->assertEquals($redirect->getStatusCode(), 200);
$this->assertEquals(count($location), 1);
$this->assertEquals($location[0], $url);
}
public function locationProvider(){
return [
['http://www.google.it'],
['http://stackoverflow.com/'],
];
}
}

Unit test Laravel middleware

I am trying to write unit tests for my middleware in Laravel. Does anyone know a tutorial, or have an example of this ?
I have been writing a lot of code, but there must be a better way to test the handle method.
Using Laravel 5.2, I am unit testing my middleware by passing it a request with input and a closure with assertions.
So I have a middleware class GetCommandFromSlack that parses the first word of the text field in my Post (the text from a Slack slash command) into a new field called command, then modifies the text field to not have that first word any more. It has one method with the following signature: public function handle(\Illuminate\Http\Request $request, Closure $next).
My Test case then looks like this:
use App\Http\Middleware\GetCommandFromSlack;
use Illuminate\Http\Request;
class CommandsFromSlackTest extends TestCase
{
public function testShouldKnowLiftCommand()
{
$request = new Illuminate\Http\Request();
$request->replace([
'text' => 'lift foo bar baz',
]);
$mw = new \App\Http\Middleware\GetCommandFromSlack;
$mw->handle($request,function($r) use ($after){
$this->assertEquals('lift', $r->input('command'));
$this->assertEquals('foo bar baz',$r->input('text'));
});
}
}
I hope that helps! I'll try to update this if I get more complicated middleware working.
To actually test the middleware class itself you can do:
public function testHandle()
{
$user = new User(['email'=>'...','name'=>'...']);
/**
* setting is_admin to 1 which means the is Admin middleware should
* let him pass, but oc depends on your handle() method
*/
$user->is_admin = 1;
$model = $this->app['config']['auth.model'];
/**
* assuming you use Eloquent for your User model
*/
$userProvider = new \Illuminate\Auth\EloquentUserProvider($this->app['hash'], $model);
$guard = new \Illuminate\Auth\Guard($userProvider, $this->app['session.store']);
$guard->setUser($user);
$request = new \Illuminate\Http\Request();
$middleware = new \YourApp\Http\Middleware\AuthenticateAdmin($guard);
$result = $middleware->handle($request, function(){ return 'can access';});
$this->assertEquals('can access',$result);
}
I thinking the best solution is just checking what happened after middleware. For example, the authentication middleware:
<?php namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class Authenticate {
/**
* The Guard implementation.
*
* #var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* #param Guard $auth
* #return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($this->auth->guest())
{
if ($request->ajax())
{
return response('Unauthorized.', 401);
}
else
{
return redirect()->guest('auth/login');
}
}
return $next($request);
}
}
And my test unit:
<?php
class AuthenticationTest extends TestCase {
public function testIAmLoggedIn()
{
// Login as someone
$user = new User(['name' => 'Admin']);
$this->be($user);
// Call as AJAX request.
$this->client->setServerParameter('HTTP_X-Requested-With', 'XMLHttpRequest');
$this->call('get', '/authpage');
$this->assertEquals(200, $response->getStatusCode());
}
}
I would do it in that way.
I was working on a localization Middleware that sets the app locale based on a URI segment, e.g. http://example.com/ar/foo should set the app local to Arabic. I basically mocked the Request object and tested as normal. Here is my test class:
use Illuminate\Http\Request;
use App\Http\Middleware\Localize;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class LocalizeMiddlewareTest extends TestCase
{
protected $request;
protected $localize;
public function setUp()
{
parent::setUp();
config(['locale' => 'en']);
config(['app.supported_locales' => ['en', 'ar']]);
$this->request = Mockery::mock(Request::class);
$this->localize = new Localize;
}
/** #test */
public function it_sets_the_app_locale_from_the_current_uri()
{
$this->request->shouldReceive('segment')->once()->andReturn('ar');
$this->localize->handle($this->request, function () {});
$this->assertEquals('ar', app()->getLocale());
}
/** #test */
public function it_allows_designating_the_locale_uri_segment()
{
$this->request->shouldReceive('segment')->with(2)->once()->andReturn('ar');
$this->localize->handle($this->request, function () {}, 2);
$this->assertEquals('ar', app()->getLocale());
}
/** #test */
public function it_throws_an_exception_if_locale_is_unsupported()
{
$this->request->shouldReceive('segment')->once()->andReturn('it');
$this->request->shouldReceive('url')->once()->andReturn('http://example.com/it/foo');
$this->setExpectedException(
Exception::class,
"Locale `it` in URL `http://example.com/it/foo` is not supported."
);
$this->localize->handle($this->request, function () {});
}
}
And here is my Middleware class:
namespace App\Http\Middleware;
use Closure;
class Localize
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param integer $localeUriSegment
* #return mixed
*/
public function handle($request, Closure $next, $localeUriSegment = 1)
{
$locale = $request->segment($localeUriSegment);
if (in_array($locale, config('app.supported_locales')))
{
app()->setLocale($locale);
}
else
{
abort(500, "Locale `{$locale}` in URL `".$request->url().'` is not supported.');
}
return $next($request);
}
}
Hope that helps :)

Mockery mock not returning specified value

I'm using Mockery in my Laravel project to mock the User Eloquent model and test a route.
This is how I test the /api/user/activate route:
<?php
use Illuminate\Support\Facades\Session;
class ActivateTest extends TestCase
{
private $userMock;
public function setUp()
{
parent::setUp();
$this->userMock = Mockery::mock('App\User');
Session::start();
}
public function tearDown()
{
Mockery::close();
}
public function testActivate()
{
$this->userMock->shouldReceive('where->first')->once()->andReturn('test');
$this->userMock->shouldReceive('activate')->once();
$response = $this->call('POST', '/api/user/activate', [
'activationToken' => '838jfjnvu83u3',
'_token' => csrf_token()
]);
// This will be displayed in the PHPunit output
print_r($response->getContent());
$this->assertResponseStatus(200);
}
}
The problem I'm having is that the andReturn('test') doesn't seem to work. The PHPunit result is:
F{"error":{"message":null,"statusCode":404}}
Time: 276 ms, Memory: 15.50Mb
There was 1 failure:
1) ActivateTest::testActivate
Failed asserting that 404 matches expected 200.
This is the content of the activate() in the UserController:
public function activate(Request $request)
{
$activation = $request->input();
$user = $this->user->where('activationToken', $activation['activationToken'])->first();
if(!$user) return $this->respondNotFound($user);
try
{
$user->activate($activation['password']);
}
catch(ModelException $e)
{
return $this->respondInternalError($e->errorMessages());
};
return $this->respondCreated('Account activated.');
}
The problem is that $user in the controller is null because the mock is not returning test (in that case the condition would evaluate to true and I wouldn't get a 404 response).
Edit:
I also tried using PHPunit mocking but it wasn't successful:
$this->userMock = $this->getMockBuilder('App\User')->setMethods(['where', 'first', 'activate'])->getMock();
$this->userMock->expects($this->once())->method('where')->willReturn($this->userMock);
$this->userMock->expects($this->once())->method('first')->willReturn('test');
$this->userMock->expects($this->once())->method('activate');
It's not enough to mock an object. You need to get that mocked object to be injected into the class which contains that activate() function.
You can do that in your setUp() function as well. Try adding this...
$this->app->instance('App/User', $this->userMock);
That will tell Laravel when you want to inject an instance of App/User, to inject the mock object you just created instead.
The issue was caused by ->first() since it's not a method existing neither on the Eloquent or User classes.
To solve it I created a new UserRepository and injected it as a dependency in the controller constructor.
class UserRepository implements UserRepositoryInterface
{
/**
* #var User
*/
protected $user;
/**
* #param User $user
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* #param $activationToken
* #return mixed
*/
public function whereActivationToken($activationToken)
{
return $this->user->where('activationToken', $activationToken)->first();
}
}
Injection in the UserController:
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
And this is how the test PostActivateTest class looks like now:
use Illuminate\Support\Facades\Session;
class PostActivateTest extends TestCase
{
private $user;
private $userRepositoryMock;
public function setUp()
{
parent::setUp();
$this->user = Mockery::mock('App\User');
$this->userRepositoryMock = Mockery::mock('Repository\Database\UserRepository');
$this->app->instance('App\User', $this->user);
$this->app->instance('Bloom\BloomCRM\Repository\Database\UserRepository', $this->userRepositoryMock);
Session::start();
}
public function tearDown()
{
Mockery::close();
}
public function testActivate()
{
$this->userRepositoryMock->shouldReceive('whereActivationToken')->once()->andReturn($this->user);
$this->user->shouldReceive('activate')->once();
$this->call('POST', '/api/user/activate', [
'activationToken' => '838jfjnvu83u3',
'password' => 'test',
'_token' => csrf_token()
]);
$this->assertResponseStatus(201);
}
}

Test blameable behavior with user in Symfony2

I'm writing a test for my entity Post, where I'm using blameable behavior from StofDoctrineExtensionsBundle. But whatever I do, there is always an error:
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'created_by' cannot be null
Part of my Post class:
namespace My\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Post
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="My\BlogBundle\Entity\PostRepository")
* #ORM\HasLifecycleCallbacks
* #ORM\ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class Post
{
/**
* #Gedmo\Blameable(on="create")
* #ORM\ManyToOne(targetEntity="My\UserBundle\Entity\User", inversedBy="posts")
* #ORM\JoinColumn(name="created_by", referencedColumnName="id", nullable=false)
*/
private $createdBy;
/* ... */
}
My 1 version of PostTest class:
namespace My\BlogBundle\Tests\Entity;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
class PostTest extends WebTestCase
{
/**
* #var \Doctrine\ORM\EntityManager
*/
private $em;
public function setUp()
{
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager()
;
}
protected function loginAs($client, $username) {
$container = $client->getContainer();
$doctrine = $container->get('doctrine');
$user = $this->loadUser($doctrine, $username);
// First Parameter is the actual user object.
// Change 'main' to whatever your firewall is called in security.yml
$container->get('security.context')->setToken(
new UsernamePasswordToken(
$user, null, 'main', $user->getRoles()
)
);
}
private function loadUser($doctrine, $username) {
// Assumes User entity implements UserInterface
return $doctrine
->getRepository('MyUserBundle:User')
->findOneByUsername($username);
}
public function testAddEntity() {
$this->loginAs(static::createClient(), 'Tester');
$newEntity = new \My\BlogBundle\Entity\Post;
$newEntity->setTitle('Test Add Entity');
$this->em->persist($newEntity);
$this->em->flush(); /* here is an error */
}
protected function tearDown()
{
parent::tearDown();
$this->em->close();
}
}
And second:
namespace My\BlogBundle\Tests\Entity;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class PostTest extends WebTestCase
{
/**
* #var \Doctrine\ORM\EntityManager
*/
private $em;
public function setUp()
{
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager()
;
}
public function testAddEntity() {
$client = static::createClient(array(), array(
'PHP_AUTH_USER' => 'Tester',
'PHP_AUTH_PW' => '1234',
));
$newEntity = new \My\BlogBundle\Entity\Post;
$newEntity->setTitle('Test Add Entity');
$this->em->persist($newEntity);
$this->em->flush(); /* here is an error */
}
protected function tearDown()
{
parent::tearDown();
$this->em->close();
}
}
Code is working fine in real controller when I'm logged in, there is Tester user in my test database (in another test I'm testing this this account and it's ok), but I can't convince blameable to use this account in test.
First PostTest class is based on this answer.

Symfony2 - Tests with FOSUserBundle

i would write a test for Symfony2 with FOSUserBundle.
At the moment i tried some ways and no one works.
I need a function like "createAuthClient".
Here is my basic class.
I post it because you could understand my problem better.
<?php
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\BrowserKit\Cookie;
class WebTestMain extends WebTestCase
{
protected static $container;
static protected function createClient(array $options = array(), array $server = array())
{
$client = parent::createClient($options, $server);
self::$container = self::$kernel->getContainer();
return $client;
}
static function createAuthClient(array $options = array(), array $server = array())
{
// see lines below with my tries
}
}
First try:
if(static::$kernel === null)
{
static::$kernel = static::createKernel($options);
static::$kernel->boot();
}
if(static::$container === null)
{
self::$container = self::$kernel->getContainer();
}
$parameters = self::$container->getParameter('test');
$server = array_merge(array('HTTP_HOST' => $parameters['host'], 'HTTP_USER_AGENT' => $parameters['useragent']), $server);
$client = self::createClient($options, $server);
$userProvider = self::$container->get('fos_user.user_manager');
$user = $userProvider->findUserBy(array('id' => 1));
$client->getCookieJar()->set(new Cookie('MOCKSESSID', true));
$session = self::$kernel->getContainer()->get('session');
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$client->getContainer()->get('security.context')->setToken($token);
$session->set('_security_main', serialize($token));
return $client;
Then i searched and searched...
My second try.
if(static::$kernel === null)
{
static::$kernel = static::createKernel($options);
static::$kernel->boot();
}
if(static::$container === null)
{
self::$container = self::$kernel->getContainer();
}
$parameters = self::$container->getParameter('test');
$server = array_merge(
array(
'HTTP_HOST' => $parameters['host'],
'HTTP_USER_AGENT' => $parameters['useragent'],
'PHP_AUTH_USER' => 'admin',
'PHP_AUTH_PW' => 'admin'
),
$server
);
$client = static::createClient(array(), array());
$client->followRedirects();
return $client;
And here is my last try before i post this question...
$client = self::createClient($options, $server);
$parameters = self::$container->getParameter('test');
$server = array_merge(
array(
'HTTP_HOST' => $parameters['host'],
'HTTP_USER_AGENT' => $parameters['useragent']
),
$server
);
$client->setServerParameters($server);
$usermanager = self::$container->get('fos_user.user_manager');
$testuser = $usermanager->createUser();
$testuser->setUsername('test');
$testuser->setEmail('test#mail.org');
$testuser->setPlainPassword('test');
$usermanager->updateUser($testuser);
return $client;
Thank you in Advance.
The best way I have found to test with an authenticated user is to just visit your login page and submit the form with user name and password you have loaded from a fixture. This may seem slow and cumbersome but will test what the user will actually do. You can even create your own method to make using it quick and easy.
public function doLogin($username, $password) {
$crawler = $this->client->request('GET', '/login');
$form = $crawler->selectButton('_submit')->form(array(
'_username' => $username,
'_password' => $password,
));
$this->client->submit($form);
$this->assertTrue($this->client->getResponse()->isRedirect());
$crawler = $this->client->followRedirect();
}
Create an AbstractControllerTest and create an authorized client on setUp() as follow:
<?php
// ...
use Symfony\Component\BrowserKit\Cookie;
abstract class AbstractControllerTest extends WebTestCase
{
/**
* #var Client
*/
protected $client = null;
public function setUp()
{
$this->client = $this->createAuthorizedClient();
}
/**
* #return Client
*/
protected function createAuthorizedClient()
{
$client = static::createClient();
$container = $client->getContainer();
$session = $container->get('session');
/** #var $userManager \FOS\UserBundle\Doctrine\UserManager */
$userManager = $container->get('fos_user.user_manager');
/** #var $loginManager \FOS\UserBundle\Security\LoginManager */
$loginManager = $container->get('fos_user.security.login_manager');
$firewallName = $container->getParameter('fos_user.firewall_name');
$user = $userManager->findUserBy(array('username' => 'REPLACE_WITH_YOUR_TEST_USERNAME'));
$loginManager->loginUser($firewallName, $user);
// save the login token into the session and put it in a cookie
$container->get('session')->set('_security_' . $firewallName,
serialize($container->get('security.context')->getToken()));
$container->get('session')->save();
$client->getCookieJar()->set(new Cookie($session->getName(), $session->getId()));
return $client;
}
}
NOTE: Please, replace the username with your test username.
Then, extends the AbstractControllerTest and use the global $client to make requests as follow:
class ControllerTest extends AbstractControllerTest
{
public function testIndexAction()
{
$crawler = $this->client->request('GET', '/admin/');
$this->assertEquals(
Response::HTTP_OK,
$this->client->getResponse()->getStatusCode()
);
}
}
This method tested and works fine