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?
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've just started with unit testing in CakePHP (yay!) and ran into the following challenge. Hope somebody can help me :-)
Situation
My model uses a Behavior to send changes to an API after saving it locally. I would like to fake all calls made to the API during the test (those will be tested seperately) to save load on the API server, and more important, not actually save the changes :-)
I'm using CakePHP 2.4.1.
What I've tried
Read the docs. The manual shows how to do this for Components and Helpers but not for Behaviors.
Google. What I've found:
A Google Group post which says it simply "isn't possible". I don't take no for an answer.
An article explaining how to mock an object. Comes pretty close.
The code from the article reads:
$provider = $this->getMock('OurProvider', array('getInfo'));
$provider->expects($this->any())
->method('getInfo')
->will($this->returnValue('200'));
It might be the wrong direction, but I think that might be a good start.
What I want
Effectively: A snippet of code to demo how to mock a behavior in a CakePHP Model for unit testing purposes.
Maybe this question will result in an addition of the CakePHP manual too as an added bonus, since I feel it's missing in there.
Thanks in advance for the effort!
Update (2013-11-07)
I've found this related question, which should answer this question (partly). No need to mock up the API, instead I can create a Behavior test that the model will use.
I'm trying to figure out what that BehaviorTest should look like.
Use the class registry
As with many classes, behaviors are added to the class registry using the class name as the key, and for subsequent requests for the same object loaded from the classregistry. Therefore, the way to mock a behavior is simply to put it in the class registry before using it.
Full Example:
<?php
App::uses('AppModel', 'Model');
class Example extends AppModel {
}
class TestBehavior extends ModelBehavior {
public function foo() {
throw new \Exception('Real method called');
}
}
class BehaviorExampleTest extends CakeTestCase {
/**
* testNormalBehavior
*
* #expectedException Exception
* #expectedExceptionMessage Real method called
* #return void
*/
public function testNormalBehavior() {
$model = ClassRegistry::init('Example');
$model->Behaviors->attach('Test');
$this->assertInstanceOf('TestBehavior', $model->Behaviors->Test);
$this->assertSame('TestBehavior', get_class($model->Behaviors->Test));
$this->assertSame(['foo' => ['Test', 'foo']], $model->Behaviors->methods());
$model->foo();
}
public function testMockedBehavior() {
$mockedBehavior = $this->getMock('TestBehavior', ['foo', 'bar']);
ClassRegistry::addObject('TestBehavior', $mockedBehavior);
$model = ClassRegistry::init('Example');
$model->Behaviors->attach('Test');
$this->assertInstanceOf('TestBehavior', $model->Behaviors->Test);
$this->assertNotSame('TestBehavior', get_class($model->Behaviors->Test));
$expected = [
'foo' => ['Test', 'foo'],
'bar' => ['Test', 'bar'],
'expects' => ['Test', 'expects'], // noise, due to being a mock
'staticExpects' => ['Test', 'staticExpects'], // noise, due to being a mock
];
$this->assertSame($expected, $model->Behaviors->methods());
$model->foo(); // no exception thrown
$mockedBehavior
->expects($this->once())
->method('bar')
->will($this->returnValue('something special'));
$return = $model->bar();
$this->assertSame('something special', $return);
}
}
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();
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.