Eh, this one makes my hair fall out...
I did some usefull stuff in zf1 and now I'm struggling to switch to zf2, and to do the thing right, I want to get stuff done TDD-style.
I've set up the Skeleton application, then made two additional modules, called "Weather" and "Airport". I than made a test case for WeatherController which works fine. Than I made a test case for models within Airport module and it fails with :
Fatal error: Class 'Airport\Model\Airport' not found in C:\xampp\htdocs...
, and the error is triggered here (AirportTableTest.php) :
<?php
namespace AirportTest\Model;
use Airport\Model\Airport;
use Airport\Model\AirportTable;
use PHPUnit_Framework_TestCase;
class AirportTableTest extends PHPUnit_Framework_TestCase {
public function testExample() {
$airport = new Airport(); // - this is not getting loaded and throws the fatal error :(
}
}
The code is based on the Album module example in ZF2 tutorial. The AirportTable model is supposed to interface a SQL table in the DB and the Airport model is written just like the Album model was written in the tutorial. The directory structure is (abbrevated) :
/module
/Airport
/src
/Airport
/Controller
/Model
AirportTable.php
Airport.php
/Application
/Weather
/public
/tests
/module
/Airport
/src
/Airport
/Controller
/Model
AirportTableTest.php
AirportTest.php
/Application
/Weather
bootstrap.php
phpunit.xml
/vendor
bootstrap.php from tests directory :
<?php
chdir(dirname(__DIR__));
error_reporting(E_ALL | E_STRICT);
include __DIR__.'/../init_autoloader.php';
The Airport.php with the class that is not being loaded :
<?php
namespace Airport\Model;
class Airport
{
public $icao;
public $lat;
public $lng;
public $metar;
public function exchangeArray($data){
$this->icao = (isset($data['id'])) ? $data['icao'] : null;
$this->lat = (isset($data['lat'])) ? $data['lat'] : null;
$this->lng = (isset($data['lng'])) ? $data['lng'] : null;
$this->metar = (isset($data['metar'])) ? $data['metar'] : null;
}
}
?>
The Module.php for Airport module :
<?php
namespace Airport;
use Airport\Model\Airport;
use Airport\Model\AirportTable;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;
class Module
{
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
public function getServiceConfig()
{
return array(
'factories' => array(
'Airport\Model\AirportTable' => function($sm) {
$tableGateway = $sm->get('AirportTableGateway');
$table = new AirportTable($tableGateway);
return $table;
},
'AirportTableGateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Airport());
return new TableGateway('airport', $dbAdapter, null, $resultSetPrototype);
},
),
);
}
}
So I'm probably missing something pretty obvious, like autoloader things related perhaps ? So, uhm... help maybe (pretty please) ?
Sooo, I came up with a working solution, although I'm not quite sure whether its smart or completly retarded.
Based on PHPUnit with a Zend Framework 2 module I added the line
Zend\Mvc\Application::init(include '/../config/application.config.php');
to the bootstrap.php of the test suite, and now everything works as expected, however I have no idea whatsoever why it would work without this line for the "Weather" module and not for "Airport" module...
You may want to have a look at the way the ZF2 getting started tutorial lays out tests. I have completed the tutorial and committed the changes to my own fork of the ZF2 Skeleton Application source.
Basically, each module has it's own test suite, with a dedicated Bootstrap file with configuration and a phpunit.xml that will tell PHPUnit to load all this when you run your test (so long as you're in the tests directory when running phpunit). This helps keep the tests modular.
Related
I hope anyone can help me out with my testing environment.
my setup
I am implementing unit tests based on phpunit 3.7.22 with the cake release 2.6.9. Running on ubuntu 12.04 LTS with PHP 5.4.43-1 and postgresql 9.1.
I immplemented controller tests mocking cakes Auth Component to have a user in the session, since my tests depent on that. My controllers return json results, since its an API for a JS-based frontend. I call my controller methods using the testAction() call of a generated controller.
<?php
App::uses('RequesttypesController', 'Svc.Controller');
class RequesttypesWithResultControllerTest extends ControllerTestCase
{
public $fixtures = array(
'app.requesttype',
'app.user',
'app.privilege',
'app.groupsprivilege',
'app.groupsuser',
'app.groupscompany',
'app.company',
);
/**
* Mock the requesttype object so that it can return results depending on the desired outcome
*
* #see CakeTestCase::setUp()
*/
public function setUp()
{
parent::setUp();
$this->controller = $this->generate('Svc.Requesttypes', array(
'models' => array(
'Requesttype'
),
'components' => array(
'Auth' => array(
'user'
),
'Session',
'RequestHandler'
)
));
$this->controller->Auth->staticExpects($this->any())
->method('user')
->will($this->returnValue(array(
'id' => 123,
'username' => 'myTestUser',
'company' => 'myTestCompany',
'usertype_id' => '456',
))
);
$authResult = $this->controller->Auth->user();
}
public function tearDown()
{
parent::tearDown();
unset($this->controller);
}
/**
* A logged in user produces a number of requesttypes
*/
public function testLoggedInUser()
{
$result = $this->testAction('/svc/requesttypes/getMyRequesttypes', array('return' => 'vars'));
$this->assertNotEmpty($this->vars, 'Did not receive webservice response');
$this->assertTrue(isset($this->vars['data']['code']), 'Received invalid webservice response');
$this->assertEqual($this->vars['data']['code'], SvcAppController::RESPONSE_CODE_SUCCESS);
}
}
?>
This test passes without errors. Now I want to test my controller-action with different setups, for example users with a different usertype, from a different company, and so on. If I now create a second test-method in my RequesttypesWithResultControllerTest-class, calling the same testAction-url, i get a MissingActionException saying:
"Action RequesttypesController::() could not be found."
It seems that the testAction calls an empty controller-action, even if the action-url is passed as a parameter. I tried reinitializing the controller by nulling it and calling $this->generate() again, but this does not help either.
Of course I can help myself out by creating an own test-controller for every test ending up in a bunch of duplicate test-code, but this somehow seems not right to me.
Am I misusing the test-environment or how can this exception be explained? Any ideas?
Thanks in advance for sharing my headache!
After some further code debugging we finally found the error. We accidently changed the require statement of the last line of the /Config/routes.php file to a require_once because of some "Class already defined Exceptions" thrown in the test-environment.
Wrong routes.php:
require_once CAKE . 'Config' . DS . 'routes.php';
For the application itself that made no difference, since the routes are only needed to be initialized once per request. But in a test-environment, the routes are reinitialized several times, which was not possible anymore with the require_once include.
This is how the line is supposed to look like, which it does by default:
Correct routes.php:
require CAKE . 'Config' . DS . 'routes.php';
I'm working in some tests for my code and I get my first "STOP" since I don't know how to mover forward on this. See in my setUp() function I load fixtures:
public function setUp() {
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->em = static::$kernel->getContainer()->get('doctrine')->getManager();
$this->user = $this->createUser();
$fix = new MetaDetailGroupFixtures();
$fix->load($this->em);
parent::setUp();
}
But then I have remove that created data since I have a test for the bad case (when not entities are returned):
public function testListMetaDetailGroupFailAction() {
$client = static::createClient();
$this->logIn($client, $this->user);
$route = $client->getContainer()->get('router')->generate('meta-detail-group-list', array('parent_id' => 20000), false);
$client->request("GET", $route);
$decoded = json_decode($client->getResponse()->getContent(), true);
$this->assertCount(0, $decoded['entities']);
$this->assertArrayHasKey('success', $decoded);
$this->assertJsonStringEqualsJsonString(json_encode(array("success" => false, "message" => "No existen grupos de metadetalles de productos creados")), $client->getResponse()->getContent());
$this->assertSame(200, $client->getResponse()->getStatusCode());
$this->assertSame('application/json', $client->getResponse()->headers->get('Content-Type'));
$this->assertNotEmpty($client->getResponse()->getContent());
}
Since records are created in setup and they remain in DB that test fail. Any advice on this? How did yours solve that?
There is no easy way to do what you ask. What is normally done is truncating the database before and after executing your tests so you have a truly clean and isolated environment.
Quoting from this nice article (http://blog.sznapka.pl/fully-isolated-tests-in-symfony2/:
public function setUp()
{
$kernel = new \AppKernel("test", true);
$kernel->boot();
$this->_application = new \Symfony\Bundle\FrameworkBundle\Console\Application($kernel);
$this->_application->setAutoExit(false);
$this->runConsole("doctrine:schema:drop", array("--force" => true));
$this->runConsole("doctrine:schema:create");
$this->runConsole("doctrine:fixtures:load", array("--fixtures" => __DIR__ . "/../DataFixtures"));
}
As you can see, this solution makes use of Doctrine's Symfony2 commands in order to achieve the isolation state. There is a bundle I like to use which solves exactly this problem and let you use nice ready to use FunctionalTest base classes and many other features. Check it out:
https://github.com/liip/LiipFunctionalTestBundle
I'm doing some tests with PHPUnit and Selenium and i would like all of them to run in the same browser window.
I've tried starting the Selenium Server with
java -jar c:\php\selenium-server-standalone-2.33.0.jar -browserSessionReuse
but with no visible change.
I've also tried with shareSession() in the setup
public function setUp()
{
$this->setHost('localhost');
$this->setPort(4444);
$this->setBrowser('firefox');
$this->shareSession(true);
$this->setBrowserUrl('http://localhost/project');
}
but the only change is that it opens a window for every test, and not really sharing the session. I'm out of ideas at this point.
My tests look like this:
public function testHasLoginForm()
{
$this->url('');
$email = $this->byName('email');
$password = $this->byName('password');
$this->assertEquals('', $email->value());
$this->assertEquals('', $password->value());
}
Here's the elegant solution. To share browser sessions in Selenium2TestCase, you must set sessionStrategy => 'shared' in your initial browser setup:
public static $browsers = array(
array(
'...
'browserName' => 'iexplorer',
'sessionStrategy' => 'shared',
...
)
);
The alternative (default) is 'isolated'.
You do not need to use the flag -browserSessionReuse
In your case The set up function running before every test and starting new instance.
This is what i did to prevent this to happen (Its little bit ugly but work for me both in Windows and Ubuntu):
I created helper class with static ver: $first and initialized it.
helper.php:
<?php
class helper
{
public static $first;
}
helper::$first = 0;
?>
Edit main test file setUp() function(and add require_once to helper.php):
require_once "helper.php";
class mySeleniumTest extends PHPUnit_Extensions_SeleniumTestCase
{
public function setUp()
{
$this->setHost('localhost');
$this->setPort(4444);
if (helper::$first == 0 )
{
$this->shareSession(TRUE);
$this->setBrowser('firefox');
$this->setBrowserUrl('http://localhost/project');
helper::$first = 1 ;
}
}
....
setHost and setPort outside the if because the values restarted after each test(For me...) and need to set up every time (if the selenium server is not localhost:4444)
Just found an (much) faster way to proceed : If you perform several test in one function, all test are performed in the same window. The setback is that the tests and reporting won't be nicely presented by tests, but the speed is way up!
In the same function for each test just use:
$this->url('...');
Or
$this->back();
I have developed a deal application using CakePHP. Now I want to write the unit-scripts using PHPUnit. I have installed PHPUnit on my server and test core tests is working fine. Installed Xdebug also for code analyze. When I am going to scripts for existing application then it is not working. I am able to write the unit-script for login menthod in model. But can't write the scripts for remaining methods.
<?php
App::uses('User', 'Model');
class UserTest extends CakeTestCase {
public $fixtures = array('app.user');
public $dropTables = false;
public function setUp() {
parent::setUp();
$this->User = ClassRegistry::init('User');
}
public function testLogin() {
$result = $this->User->find('count', array(
'conditions' => array(
// making some assumptions about the test data here
'email' => 'test.user#gmail.com',
'password' => 'f1054da373ace628dc73b8ec52eb28072b074940',
),));
$expected = 1;
$this->assertEquals($expected, $result);
}
}
?>
it is working well. But I am not able to write scripts for the remaining methods.
Try using bake and bake the tests to get started with the correct testing structure:
cake bake test
This might help push you in the write direction with the way Cake expects the test to be structured. It will also create the empty methods to test all of the methods in the controller.
Can you be more specific about what is not working?
Does anyone here use Zend Framework, ZFDoctrine and PHPUnit together?
How to rebuild the database on each test run?
How to separate local/production/testing environments?
Would you share your unit testing setup?
I have been trying something like that:
// /tests/bootstrap.php
// ... setup paths and constants here
require_once 'Zend/Application.php';
// Create application, bootstrap, and run
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);
$application->bootstrap('doctrine');
$provider = new ZFDoctrine_Tool_DoctrineProvider;
$provider->generateModelsFromYaml();
//$provider->buildProject(true);
But this ends in:
Notice: Constant APPLICATION_PATH already defined in /home/user/www/library/ZendFramework/1.10.7/library/Zend/Tool/Project/Context/Zf/BootstrapFile.php on line 106
Fatal error: Call to a member function getResponse() on a non-object in /home/user/www/library/zf-doctrine/library/ZFDoctrine/Tool/DoctrineProvider.php on line 271
Models are not generated.
I get similar errors running:
$provider->createDatabase();
But in this case database is created.
The other provider commands do not work.
The solution:
$provider = new ZFDoctrine_Tool_DoctrineProvider;
$registry = new Zend_Tool_Framework_Registry;
$provider->setRegistry($registry);
#$provider->buildProject(true);
If anybody knows a better approach, please correct me.
I haven't used ZFDoctrine, but just plain Doctrine 1.2. I don't know if my solution is better but I figured I post if any1 is interested, here's the bootstrap.php in my tests folder:
<?php
// Define path to application directory
defined('APPLICATION_PATH')
|| define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../../application'));
// Define application environment
/**
* In the application.ini:
[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
doctrine.dsn = "mysql://my_user:passwd#localhost/my_phpunit_test_db"
*/
define('APPLICATION_ENV', 'testing');
// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
realpath(APPLICATION_PATH . '/../library'),
get_include_path()
)));
/** Zend_Application */
require_once 'Zend/Application.php';
// Create application, bootstrap, and run
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/../configs/application.ini'
);
$application->getBootstrap()->bootstrap();
// Can run out if too small
ini_set('memory_limit', '512M');
// Get the doctrine settings
$config = $application->getOption('doctrine');
$cli = new Doctrine_Cli($config);
$cli->run(array("doctrine", "build-all-reload","force"));
The key here is actually the last line that rebuilds all databases creating a clean environment for each testing.