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
Related
I have Post and Tag entities with many-to-many relationship. In Post create and edit form there is a textbox where I can enter tags separated by comma relevant to that post. For example, when I enter tag1, tag2, tag3 for post with title 'Post1', the form will create post and tag entities and add these tags to tags list of that post.I use data transformer to create tag entities.
class Post{
/**
* #ORM\ManyToMany(targetEntity="Tag", mappedBy="posts",cascade={"all"})
*/
protected $tags;
public function __construct() {
$this->tags = new ArrayCollection();
}
/**
* #return ArrayCollection
*/
public function getTags()
{
return $this->tags;
}
/**
* #param Tag $tag
*/
public function addTag(Tag $tag)
{
$tag->addPost($this);
$this->tags->add($tag);
}
/**
* #param Tag $tag
*/
public function removeTag(Tag $tag)
{
$this->tags->removeElement($tag);
}
}
PostType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, array('label' => 'Title'))
->add('tags', TextType::class, array('label' => 'Tags'))
;
$builder->get('tags')
->addModelTransformer(new TagViewTransformer($this->manager));
}
TagViewTransformer
class TagViewTransformer implements DataTransformerInterface
{
public function transform($value)
{
/...
}
public function reverseTransform($value)
{
$tags = array();
if ( $value )
{
if( strpos($value, ',') !== false )
{
$list = array_unique(explode(",", $value));
}
else
{
$list = array(trim($value));
}
foreach ( $list AS $tagName )
{
$tag = $this->em
->getRepository('CoreBundle:Tag')
->findOneBy(array('name' => trim($tagName)));
if( !$tag )
{
$tag = new Tag();
$tag->setName(trim($tagName));
$this->em->persist($tag);
}
$tags[] = $tag;
}
}
return $tags;
}
}
This works fine when I try to create Post, all tags are transformed to entities and are added to Post's tags list. but when I try to edit, I start having problems
public function editAction(Request $request, Post $post)
{
$deleteForm = $this->createDeleteForm($post);
$editForm = $this->createForm(PostType::class, $post);
$editForm->handleRequest($request);
$originalTags = $post->getTags();
if ($editForm->isSubmitted() && $editForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$newTags = $editForm->get('tags')->getData();
foreach ($originalTags as $currentTag) {
if (!in_array($currentTag, $newTags)) {
$post->removeTag($currentTag);
}
}
$em->persist($post);
$em->flush();
return $this->redirectToRoute('post_show', array('id' => $post->getId()));
}
return $this->render('AppBundle:Post:edit.html.twig', array(
'entity' => $post,
'form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
Let's say Post has tags: tag1, tag2, tag3, but I want to remove tag3 and add tag4. So I will change tags textbox to tag1, tag2, tag4. However when I submit form, I get tag1, tag2, tag3, tag4. So tag3 is not removed from Post's tag list.
What is wrong with the editAction code?
try with this
public function editAction(Request $request, Post $post)
{
$deleteForm = $this->createDeleteForm($post);
$editForm = $this->createForm(PostType::class, $post);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($post);
$em->flush();
return $this->redirectToRoute('post_show', array('id' => $post->getId()));
}
return $this->render('AppBundle:Post:edit.html.twig', array(
'entity' => $post,
'form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
Use Orphan removal to do this :
class Post
{
/**
* #ORM\ManyToMany(targetEntity="Tag", mappedBy="posts", cascade={"all"}, orphanRemoval=true)
*/
protected $tags;
/* Rest of your class */
}
Do some tests, maybe cascade all is not necessary here.
When I try to execute this unit test
I have a problem
so this is my function testErrors
.
public function testErrors(){
$client = static::createClient();
$crawler = $client->request('GET', '/add');
$form = $crawler->selectButton('save')->form(array(
'user[firstName]' => 'test1',
'user[lastName]' => 'test',
'user[email]' => 'test#gmail.com',
));
$crawler = $client->submit($form);
// 3 errors
$this->assertTrue($crawler->filter('.error_list')->count() == 3);
// Error firstName field
$this->assertTrue($crawler->filter('#firstName')->siblings()->first()->filter('.error_list')->count() == 1);
// Error lasName field
$this->assertTrue($crawler->filter('#lastName')->siblings()->first()->filter('.error_list')->count() == 1);
// Error email field
$this->assertTrue($crawler->filter('#email')->siblings()->first()->filter('.error_list')->count() == 1);
}
I have this problem
InvalidArgumentException: The current node list is empty .
this is my Controller
/**
* #Route("/add", name="addPage")
*/
public function AddAction(Request $request)
{
$user = new User();
$form = $this->createFormBuilder($user)
->add('email', TextType::class)
->add('firstName', TextType::class)
->add('lastName', TextType::class)
->add('save', SubmitType::class, array('label' => 'Add'))
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$firstName = $form['firstName']->getData();
$lastName = $form['lastName']->getData();
$email = $form['email']->getData();
$user->setFirstName($firstName);
$user->setLastName($lastName);
$user->setEmail($email);
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
$this->addFlash('notice','user added' );
return $this->redirectToRoute('listPage');
}
I think it's your selectButton('save') what gives you the error. Try it with your button label Add instead of save
I want to test a model and for one of those tests I want to mock a method of the model I am testing. So I don't test a controller and I don't want to replace a whole model, just one method of the same model I test.
Reason is that this model method calls a file upload handler. This feature is already tested elsewhere.
What I am doing now is:
I test the model 'Content'. There I test it's method 'addTeaser', which calls 'sendTeaser'.
SO I want to mock sendTeaser and fake a successful answer of the method sendTeaser, while testing addTeaser.
That looks like this:
$model = $this->getMock('Content', array('sendTeaser'));
$model->expects($this->any())
->method('sendTeaser')
->will($this->returnValue(array('ver' => ROOT.DS.APP_DIR.DS.'webroot/img/teaser/5/555_ver.jpg')));
$data = array(
'Content' => array(
'objnbr' => '555',
'name' => '',
...
)
)
);
$result = $model->addTeaser($data);
$expected = true;
$this->assertEquals($expected, $result);
When I let my test run, I get an error that a model within the method 'sendTeaser' is not called properly. Hey! It shouldn't be called! I mocked the method!
..... or not?
What would be the proper syntax for mocking the method?
Thanks a lot as always for help!
Calamity Jane
Edit:
Here is the relevant code for my model:
App::uses('AppModel', 'Model');
/**
* Content Model
*
* #property Category $Category
*/
class Content extends AppModel {
public $dateipfad = '';
public $fileName = '';
public $errormessage = '';
public $types = array(
'sqr' => 'square - more or less squarish',
'hor' => 'horizontal - clearly wider than high',
'lnd' => 'landscape - low but very wide',
'ver' => 'column - clearly higher than wide',
);
public $order = "Content.id DESC";
public $actsAs = array('Containable');
public $validateFile = array(
'size' => 307200,
'type' => array('jpeg', 'jpg'),
);
//The Associations below have been created with all possible keys, those that are not needed can be removed
public $hasMany = array(
'CategoriesContent' => array(
'className' => 'CategoriesContent',
),
'ContentsTag' => array(
'className' => 'ContentsTag',
),
'Description' => array(
'className' => 'Description',
)
);
/**
* Saves the teaser images of all formats.
*
* #param array $data
*
* #return Ambigous <Ambigous, string, boolean>
*/
public function addTeaser($data)
{
$objnbr = $data['Content']['objnbr'];
$type = $data['Content']['teaser-type'];
if (!empty($data['Content']['teaser-img']['tmp_name'])) {
$mFileNames = $this->sendTeaser($data, $objnbr, $type);
}
if (!is_array($mFileNames)) {
$error = $mFileNames;
//Something failed. Remove the image uploaded if any.
$this->deleteMovedFile(WWW_ROOT.IMAGES_URL.$mFileNames);
return $error;
}
return true;
}
/**
* Define imagename and save the file under this name.
*
* Since we use Imagechache, we don't create a small version anymore.
*
* #param integer $objnbr
* #param string $teasername
*
* #return multitype:Ambigous <string, boolean> |Ambigous <boolean, string>
*/
public function sendTeaser($data, $objnbr, $type)
{
//$path = str_replace('htdocs','tmp',$_SERVER['DOCUMENT_ROOT']);
$this->fileName = $this->getImageName($objnbr, $type);
$oUH = $this->getUploadHandler($data['Content']['teaser-img']);
debug($oUH);
exit;
$error = $oUH->handleFileUpload();
if (empty($type))
$type = 0;
if ($error === 'none'){
// Send to ImageChacheServer
$oICC = $this->getImagecacheConnector();
$sCacheUrl = $oICC->uploadFile($objnbr, $type, $this->fileName);
debug($sCacheUrl);
return array($type => $this->fileName);
}
return $error;
}
public function getUploadHandler($imgdata)
{
App::uses('UploadHandler', 'Lib');
$oUH = new UploadHandler($this, $imgdata);
return $oUH;
}
}
Changing getMock to getMockForModel didn't change the output though.
I'd like to emphasize the answer from #ndm using Cake test helper class CakeTestCase::getMockForModel()
$theModel = CakeTestCase::getMockForModel('Modelname', ['theMethodToMock']);
$theModel->expects($this->once())
->method('theMethodToMock')
->will($this->returnValue('valueToReturn'));
$this->getMock is not the way to mock. You should use $this->generate
I would reccomend you to read a book about CakePHP unti testing, like this: https://leanpub.com/cakephpunittesting
I'm unit testing my controller called IndexController (used the http://framework.zend.com/manual/2.1/en/user-guide/modules.html tutorial).
My IndexController has a dependency:Translator $translator.
<?php
// module/Application/src/Application/Controller/IndexController.php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\I18n\Translator\Translator;
class IndexController extends AbstractActionController
{
protected $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
public function indexAction()
{
$steeringWheelMapper = $this->getServiceLocator()->get('SupplierName\Mapper\SteeringWheel');
$carBrandList = $steeringWheelMapper ->fetchCarBrandList();
return new ViewModel();
}
}
My unit test setup:
module
Application
test
ApplicationTest
Controller
IndexControllerTest.php
Bootstrap.php
phpunit.xml.dist
TestConfig.php.dist
Testing now gives the following error:
Argument 1 passed to Application\Controller\IndexController::__construct() must be an instance of Zend\I18n\Translator\Translator, none given.
Nothing strange here, but I can't find out where to add the translator object..
Where should I add the translator object?
The code
<?php
// module/Application/test/Testconfig.php.dist
return array(
'modules' => array(
'Application',
),
'module_listener_options' => array(
'config_glob_paths' => array(
'../../../config/autoload/{,*.}{global,local}.php',
),
'module_paths' => array(
'module',
'vendor',
),
),
);
<?php
// module/Application/test/Bootstrap.php
namespace ApplicationTest;//Change this namespace for your test
use Zend\Loader\AutoloaderFactory;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
use Zend\Stdlib\ArrayUtils;
use RuntimeException;
error_reporting(E_ALL | E_STRICT);
chdir(__DIR__);
class Bootstrap
{
protected static $serviceManager;
protected static $config;
protected static $bootstrap;
public static function init()
{
// Load the user-defined test configuration file, if it exists; otherwise, load
if (is_readable(__DIR__ . '/TestConfig.php')) {
$testConfig = include __DIR__ . '/TestConfig.php';
} else {
$testConfig = include __DIR__ . '/TestConfig.php.dist';
}
$zf2ModulePaths = array();
if (isset($testConfig['module_listener_options']['module_paths'])) {
$modulePaths = $testConfig['module_listener_options']['module_paths'];
foreach ($modulePaths as $modulePath) {
if (($path = static::findParentPath($modulePath)) ) {
$zf2ModulePaths[] = $path;
}
}
}
$zf2ModulePaths = implode(PATH_SEPARATOR, $zf2ModulePaths) . PATH_SEPARATOR;
$zf2ModulePaths .= getenv('ZF2_MODULES_TEST_PATHS') ?: (defined('ZF2_MODULES_TEST_PATHS') ? ZF2_MODULES_TEST_PATHS : '');
static::initAutoloader();
// use ModuleManager to load this module and it's dependencies
$baseConfig = array(
'module_listener_options' => array(
'module_paths' => explode(PATH_SEPARATOR, $zf2ModulePaths),
),
);
$config = ArrayUtils::merge($baseConfig, $testConfig);
$serviceManager = new ServiceManager(new ServiceManagerConfig());
$serviceManager->setService('ApplicationConfig', $config);
$serviceManager->get('ModuleManager')->loadModules();
static::$serviceManager = $serviceManager;
static::$config = $config;
}
public static function getServiceManager()
{
return static::$serviceManager;
}
public static function getConfig()
{
return static::$config;
}
protected static function initAutoloader()
{
$vendorPath = static::findParentPath('vendor');
if (is_readable($vendorPath . '/autoload.php')) {
$loader = include $vendorPath . '/autoload.php';
} else {
$zf2Path = getenv('ZF2_PATH') ?: (defined('ZF2_PATH') ? ZF2_PATH : (is_dir($vendorPath . '/ZF2/library') ? $vendorPath . '/ZF2/library' : false));
if (!$zf2Path) {
throw new RuntimeException('Unable to load ZF2. Run `php composer.phar install` or define a ZF2_PATH environment variable.');
}
include $zf2Path . '/Zend/Loader/AutoloaderFactory.php';
}
AutoloaderFactory::factory(array(
'Zend\Loader\StandardAutoloader' => array(
'autoregister_zf' => true,
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/' . __NAMESPACE__,
),
),
));
}
protected static function findParentPath($path)
{
$dir = __DIR__;
$previousDir = '.';
while (!is_dir($dir . '/' . $path)) {
$dir = dirname($dir);
if ($previousDir === $dir) return false;
$previousDir = $dir;
}
return $dir . '/' . $path;
}
}
Bootstrap::init();
<?php
// module/Application/test/ApplicationTest/Controller/IndexControllerTest.php
namespace ApplicationTest\Controller;
use ApplicationTest\Bootstrap;
use Zend\Mvc\Router\Http\TreeRouteStack as HttpRouter;
use Application\Controller\IndexController;
use Zend\Http\Request;
use Zend\Http\Response;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Router\RouteMatch;
use PHPUnit_Framework_TestCase;
class IndexControllerTest extends \PHPUnit_Framework_TestCase
{
protected $controller;
protected $request;
protected $response;
protected $routeMatch;
protected $event;
protected function setUp()
{
$serviceManager = Bootstrap::getServiceManager();
$this->controller = new IndexController();
$this->request = new Request();
$this->routeMatch = new RouteMatch(array('controller' => 'index'));
$this->event = new MvcEvent();
$config = $serviceManager->get('Config');
$routerConfig = isset($config['router']) ? $config['router'] : array();
$router = HttpRouter::factory($routerConfig);
$this->event->setRouter($router);
$this->event->setRouteMatch($this->routeMatch);
$this->controller->setEvent($this->event);
$this->controller->setServiceLocator($serviceManager);
}
public function testIndexActionCanBeAccessed()
{
$this->routeMatch->setParam('action', 'index');
$result = $this->controller->dispatch($this->request);
$response = $this->controller->getResponse();
$this->assertEquals(200, $response->getStatusCode());
}
}
Inject it in your setUp method
protected function setUp()
{
// ...
$translator = new \Zend\I18n\Translator;
$this->controller = new IndexController($translator);
// ....
}
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