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 :)
Related
I have problem with my test. I learn how to write phpunit test and how i can mock object, services etc.. I have this method on my ProductService:
<?php
namespace App\Service;
use App\Entity\Product;
use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\ORMException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class ProductService
{
/**
* #var ProductRepository
*/
private $productRepository;
/**
* #var EntityManager
*/
private $entityManager;
/**
* #var ValidatorInterface
*/
private $validator;
/**
* ProductService constructor.
* #param ProductRepository $productRepository
* #param EntityManagerInterface $entityManager
* #param ValidatorInterface $validator
*/
public function __construct(ProductRepository $productRepository, EntityManagerInterface $entityManager, ValidatorInterface $validator)
{
$this->productRepository = $productRepository;
$this->entityManager = $entityManager;
$this->validator = $validator;
}
/**
* #param $productData
* #return Product|string
*/
public function createProduct($productData)
{
$name = $productData['name'];
$quantity = $productData['quantity'];
$sku = $productData['sku'];
$product = new Product();
$product->setName($name);
$product->setQuantity($quantity);
$product->setProductSerial($sku);
$errors = $this->validator->validate($product);
if (count($errors) > 0) {
$errorsString = (string)$errors;
return $errorsString;
}
try {
$this->entityManager->persist($product);
$this->entityManager->flush();
return $product;
} catch (\Exception $ex) {
return $ex->getMessage();
}
}
}
and i write this test:
<?php
namespace App\Tests\Service;
use App\Entity\Product;
use App\Repository\ProductRepository;
use App\Service\ProductService;
use Doctrine\Common\Persistence\ObjectRepository;
use PHPUnit\Framework\TestCase;
class ProductServiceTest extends TestCase
{
/**
* Create product test
*/
public function testCreateProduct()
{
$product = new Product();
$product->setName('tester');
$product->setQuantity(2);
$product->setProductSerial('Examplecode');
$productService = $this->createMock(ProductService::class);
$productService->method('createProduct')->will($this->returnSelf());
$this->assertSame($productService, $productService->createProduct($product));
}
}
When i run phpunit test, then i always have success but my database is empty. How can I be sure that the test works correctly? What is worth fixing and what is not? I wanted to make the launch of tests result in, for example, adding records to the test database, but I have no idea how to do it and how to properly mock it. I using phpunit + Symfony 4.
I used to write tests, but those that asked the endpoint API, and here I want to test services and repositories without endpoints.
I would like to learn how to test and mock websites, repositories, various classes etc.
When i apply answer then i have:
PHPUnit 7.5.17 by Sebastian Bergmann and contributors.
Testing Project Test Suite
?[31;1mE?[0m 1 / 1 (100%)
Time: 542 ms, Memory: 10.00 MB
There was 1 error:
1) App\Tests\Service\ProductServiceTest::testCreateProduct
Doctrine\Common\Persistence\Mapping\MappingException: The class 'App\Repository\ProductRepository' was not found in the chain configured namespaces App\Entity, Gesdinet\JWTRefreshTokenBundle\Entity
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\Mapping\MappingException.php:22
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain.php:87
D:\warehouse-management-api\vendor\doctrine\orm\lib\Doctrine\ORM\Mapping\ClassMetadataFactory.php:151
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory.php:304
D:\warehouse-management-api\vendor\doctrine\orm\lib\Doctrine\ORM\Mapping\ClassMetadataFactory.php:78
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory.php:183
D:\warehouse-management-api\vendor\doctrine\orm\lib\Doctrine\ORM\EntityManager.php:283
D:\warehouse-management-api\vendor\doctrine\doctrine-bundle\Repository\ContainerRepositoryFactory.php:44
D:\warehouse-management-api\vendor\doctrine\orm\lib\Doctrine\ORM\EntityManager.php:713
D:\warehouse-management-api\vendor\doctrine\persistence\lib\Doctrine\Common\Persistence\AbstractManagerRegistry.php:215
D:\warehouse-management-api\tests\Service\ProductServiceTest.php:28
?[37;41mERRORS!?[0m
?[37;41mTests: 1?[0m?[37;41m, Assertions: 0?[0m?[37;41m, Errors: 1?[0m?[37;41m.?[0m
My Product entity
<?php
namespace App\Entity;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProductRepository")
*/
class Product
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank()
*/
private $name;
/**
* #ORM\Column(type="integer")
* #Assert\NotBlank()
*/
private $quantity;
/**
* #Gedmo\Mapping\Annotation\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #Gedmo\Mapping\Annotation\Timestampable(on="update")
* #ORM\Column(type="datetime")
*/
private $updatedAt;
/**
* #ORM\Column(type="string")
* #Assert\NotBlank()
*/
private $product_serial;
public function __construct() {
$this->setCreatedAt(new \DateTime());
$this->setUpdatedAt();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getQuantity(): ?int
{
return $this->quantity;
}
public function setQuantity(int $quantity): self
{
$this->quantity = $quantity;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(): self
{
$this->updatedAt = new \DateTime();
return $this;
}
public function getProductSerial(): ?string
{
return $this->product_serial;
}
public function setProductSerial(string $product_serial): self
{
$this->product_serial = $product_serial;
return $this;
}
}
ProductRepository
<?php
namespace App\Repository;
use App\Entity\Product;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
class ProductRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Product::class);
}
}
doctrine.yaml
doctrine:
dbal:
# configure these for your database server
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
First of all, when you mock a method the original method doesn't exist any more, in this test. In your case you substitute ProductService::createProduct with something like this:
// This is your mock
class ProductService
{
// ...
public function createProduct($productData)
{
return $this;
}
}
Your test doesn't check anything.
If you want to test the real functionality then
namespace App\Tests\Service;
use App\Repository\ProductRepository;
use App\Service\ProductService;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class ProductServiceTest extends KernelTestCase
{
/**
* Create product test
*/
public function testCreateProduct(): void
{
// We load the kernel here (and $container)
self::bootKernel();
$productData = [
'name' => 'foo',
'quantity' => 1,
'sku' => 'bar',
];
$productRepository = static::$container->get('doctrine')->getRepository(ProductRepository::class);
$entityManager = static::$container->get('doctrine')->getManager();
// Here we mock the validator.
$validator = $this->getMockBuilder(ValidatorInterface::class)
->disableOriginalConstructor()
->setMethods(['validate'])
->getMock();
$validator->expects($this->once())
->method('validate')
->willReturn(null);
$productService = new ProductService($productRepository, $entityManager, $validator);
$productFromMethod = $productService->createProduct($productData);
// Here is you assertions:
$this->assertSame($productData['name'], $productFromMethod->getName());
$this->assertSame($productData['quantity'], $productFromMethod->getQuantity());
$this->assertSame($productData['sku'], $productFromMethod->getSku());
$productFromDB = $productRepository->findOneBy(['name' => $productData['name']]);
// Here we test that product in DB and returned product are same
$this->assertSame($productFromDB, $productFromMethod);
}
}
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/'],
];
}
}
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);
}
}
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.
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