How to unit test code with Phalcon MVC Models

I started my new job last year, after a few good years of working within an operational department which involved mainly bug fixing. The new job is more of a greenfield development than bug fixing. This meant an opportunity to apply what I’ve learnt over the past few years and strategically choose a framework that’s both fast and simple to use. At that time, Phalcon v1.2.3 had just made it to master and we decided it was the right framework for what we needed.

Project development started and the work completed along with it’s unit test. As part of this I made my first public git commit. My first contribution to the PHP community in general, and Phalcon folks in particular, was the introduction of MVC Functional Testing with PHPUnit, this meant that Applications developed using Phalcon Framework could be unit tested by requesting a url then asserting that the response is handled by a specific action within the controller. I was very pleased to see the pull request getting merged into Phalcon Incubator. Furthermore, seeing it being maintained by other developers meant it was being used and continues to be cared for.

To date, my latest addition is an example of how to unit test code that utilises Phalcon models, mainly the various static find methods. The code is available on github and the coverage report is coveralls.io.

The reason behind this work was due to the fact that we were either; wrapping these static find methods by a protected function or completely avoiding unit testing these methods. Both of these unsettled me. Now that I’ve managed to get this done, I’m looking forward to ploughing through the backlog and get those lines even greener.

Enough said, let’s step into the code

In order to unit test models without a database connection, we need to convince the model manager that we have our table meta data cached. To do this, I had to use ‘Phalcon\Mvc\Model\MetaData\Files’ for the ‘modelsMetadata’ service. This meant that I could use the same cache files generated by Phalcon, therefore Phalcon doesn’t need to interrogate the information schema for the table in question.

The model under test is for the table below:

CREATE TABLE Popup

(

Id INT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT,

Name VARCHAR(24) NOT NULL,

Conent VARCHAR(200) NOT NULL,

CreatedAt DATETIME NOT NULL,

LastUpdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL

);

CREATE INDEX idxName ON Popup ( Name );

Phalcon generated a couple of cache files for this table:

tests/resources/map-silverbadge_models_popup.php

<?php return array (

0 => NULL,

1 => NULL,

);

tests/resources/meta-silverbadge_models_popup-testpopup.php

<?php return array (

0 =>

array (

0 => 'Id',

1 => 'Name',

2 => 'Conent',

3 => 'CreatedAt',

4 => 'LastUpdate',

),

1 =>

array (

0 => 'Id',

),

2 =>

array (

0 => 'Name',

1 => 'Conent',

2 => 'CreatedAt',

3 => 'LastUpdate',

),

3 =>

array (

0 => 'Id',

1 => 'Name',

2 => 'Conent',

3 => 'CreatedAt',

4 => 'LastUpdate',

),

4 =>

array (

'Id' => 0,

'Name' => 2,

'Conent' => 2,

'CreatedAt' => 4,

'LastUpdate' => 1,

),

5 =>

array (

'Id' => true,

),

8 => 'Id',

9 =>

array (

'Id' => 1,

'Name' => 2,

'Conent' => 2,

'CreatedAt' => 2,

'LastUpdate' => 2,

),

10 =>

array (

),

11 =>

array (

),

);

As you can see both of the files now live in the tests/resources directory and will need to be maintained should the table’s structure change. In my opinion, this is more of a best practice than an overhead. An alternative to this is to use ‘Annotations Strategy’, however this may add more development time and can easily get inconsistent with the table structure in the future.

The model under test is below:

src/Silverbadge/Models/Popup.php

<?php

namespace Silverbadge\Models;



use Phalcon\Mvc\Model;



class Popup extends Model

{

/**

*

* @var integer

*/

protected $Id;



/**

*

* @var string

*/

protected $Name;



/**

*

* @var string

*/

protected $Content;



/**

*

* @var string

*/

protected $CreatedAt;



/**

*

* @var string

*/

protected $LastUpdate;



/**

* @param string $Content

*

* @return Popup

*/

public function setContent($Content)

{

$this->Content = $Content;

return $this;

}



/**

* @return string

*/

public function getContent()

{

return $this->Content;

}



/**

* @param string $Name

*

* @return Popup

*/

public function setName($Name)

{

$this->Name = $Name;

return $this;

}



/**

* @return string

*/

public function getName()

{

return $this->Name;

}



/**

* @param string $LastUpdate

*

* @return Popup

*/

public function setLastUpdate($LastUpdate)

{

$this->LastUpdate = $LastUpdate;

return $this;

}



/**

* @return string

*/

public function getLastUpdate()

{

return $this->LastUpdate;

}


/**

* @param int $Id

*

* @return Popup

*/

public function setId($Id)

{

$this->Id = $Id;

return $this;

}



/**

* @return int

*/

public function getId()

{

return $this->Id;

}


/**

* @param string $CreatedAt

*

* @return Popup

*/

public function setCreatedAt($CreatedAt)

{

$this->CreatedAt = $CreatedAt;

return $this;

}



/**

* @return string

*/

public function getCreatedAt()

{

return $this->CreatedAt;

}


public function getSchema()

{

return 'test';

}


public function getSource()

{

return 'Popup';

}


/**

* Initialize method for model.

*/

public function initialize()

{

$this->useDynamicUpdate(true);

}

}

And the bit of code that uses the model is below, this single method class has been prepared to illustrate how to unit test both Popup::find() and the save method, nothing overtly complex for this class.

src/Silverbadge/Api/Facade.php

<?php

/**

* Facade.php

*

* @author Rami

*/


namespace Silverbadge\Api;


use Phalcon\Mvc\User\Component;

use Silverbadge\Models\Popup;


class Facade extends Component

{

public function createNewPopup($name, $content)

{

$response = array();

$content = strip_tags($content);

if (empty($content) || empty($name) || count(Popup::find(array("Name = '$name'"))->toArray()) > 0) {

$response['code'] = 400;

$response['message'] = "Bad request, empty parameters supplied or popup with this name already exists";

} else {

$popup = new Popup();

$popup->save(

array(

'Name' => $name,

'Content' => $content,

'CreatedAt' => date('Y-m-d H:i:s'),

'LastUpdate' => date('Y-m-d H:i:s')

)

);


$response['Name'] = $name;

$response['Content'] = $content;

$response['Id'] = $popup->getId();

}

return $response;

}

}

And we come to the unit test, it’s worth noting that for reasons that are unknown to me, phpunit fails with a weird error when running the test with a DataProvider. The only way to get it to pass was by forcing the test to run in a separate process using the @runInSeparateProcess annotation.

tests/Tests/Silverbadge/Api/FacadeTest.php

<?php

/**

* FacadeTest.php

*

* @author rami

*/



namespace Tests\Silverbadge\Api;



use Silverbadge\Api\Facade;

use Phalcon\Mvc\Model\Manager;

use Phalcon\Mvc\Model\Metadata\Files;

use Phalcon\DI;



class FacadeTest extends \PHPUnit_Framework_TestCase

{

/**

* @dataProvider provideDataForTestCreateApplication

* @runInSeparateProcess

*/

public function testCreateApplication($numRowsFound, $fetchAllData)

{

$di = New DI();

$di->set('modelsManager', new Manager());

$di->set('modelsMetadata', new Files(array('metaDataDir' => __DIR__ . '/../../../resources/')));

$con = $this->getMock('\\Phalcon\\Db\\Adapter\\Pdo\\Mysql', array('getDialect', 'query', 'execute'), array(),'',false);

$dialect = $this->getMock('\\Phalcon\\Db\\Dialect\\Mysql', array('select'), array(), '', false);

$results = $this->getMock('\\Phalcon\\Db\\Result\\Pdo', array('numRows', 'setFetchMode', 'fetchall'), array(), '', false);

$results->expects($this->any())

->method('numRows')

->will($this->returnValue($numRowsFound));



$results->expects($this->any())

->method('fetchall')

->will($this->returnValue($fetchAllData));



$dialect->expects($this->any())

->method('select');



$con->expects($this->any())

->method('getDialect')

->will($this->returnValue($dialect));



$con->expects($this->any())

->method('query')

->will($this->returnValue($results));



$con->expects($this->any())

->method('execute');


$di->set('db', $con);



$facade = new Facade();

$facade->setDI($di);



$actual = $facade->createNewPopup('Test Popup','This is a test');



$this->assertTrue(is_array($actual));

}



public function provideDataForTestCreateApplication()

{

return array(

array(0, null),

array(1, array(array())),

);

}

}

So,there you go, 100% code coverage without mocking away methods that wraps the static model find methods or turning a blind eye on unit testing just because of Models.

Conclusion

Phalcon is a very flexible framework and can be unit tested with a bit of researching and code tracing, we can not only make our code green on our build boxes but also more meaningful and readable.

Tagged with: , , , , ,
Posted in phalcon, Unit testing

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

In Archive