JaggyGauran

Freelance developer, and designer

CakePHP Factory Plug-in

Now I've grown to know and love unit testing while I was still doing some Rails and I loved the concept of factories over fixtures. Jumping into CakePHP, the testing felt a bit alien to me so I made a Factory plug-in to suit my Model testing needs.

Check it out here.

Installation

Clone the application inside your Plugins folder

$ git clone https://github.com/jaggypsaghetti/cake-factory.git Factory

Load it and make sure you read the Plugin's bootstrap folder. Append this to your Config/bootstrap.php:

CakePlugin::load( 'Factory', array('bootstrap' => true) );

Settings

You can set the factory location here: Plugin/Factory/Config/bootstrap.php

define('FACTORY', ROOT . DS . APP_DIR . DS . 'Test' . DS . 'Factory');

By default, you can find it in your Test/Factory/

Usage

Let's say we are testing a Model User.php and Role.php and we have our UserTest.php and RoleTest.php.

To start things up, we need to create our Factories: Test/Factory/User.json and Test/Factory/Role.json

User.json

{
    "username":              "example.username",
    "password":              "weakpassword",
    "password_confirmation": "weakpassword",
    "role_id": {
        "model": "Role"
    }
}

Role.json

{
    "name": "Administrator"
}

As you can see immediately, we can create associations through the factory itself. The plugin reads the json files and builds the model.

NOTE: Make sure that the factory is the type of data you want to pass rather than fail.

UserTest.php

To use factories, Let's have a class that looks like this:

<?php

App::uses('User', 'Model');
App::uses('Factory', 'Factory.Lib');

class UserTest extends CakeTestCase
{

    public $fixtures = [ 'app.user', 'app.role' ];

    protected $factory;

      /**
       + setUp method
       *
       + @return void
       */
    public function setUp()
    {
        parent::setUp();

        $this->User= ClassRegistry::init( 'User' );
        $this->factory = new Factory( 'user' );
    }


    /**
     + tearDown method
     *
     + @return void
     */
    public function tearDown()
    {
        unset( $this->User );

        parent::tearDown();
    }

}

Let's make some tests!

Let's say our model fields are all required, let's make sure our models do not accept null values.

function testDoesNotAcceptABlankUsername()
{
    $_factory = $this->factory->build(['username' => null]);

    $this->assertFalse( $_factory->validates() );
}

By using build() we are creating a model ready for insertion prefilled with the User.json values. By passing an array through it, we are overriding those attributes with our own so we can tests invalid inserts.

You can also use validate() if you just need a boolean return whether the attributes you passed give off the green or red light.

function testDoesNotAcceptABlankPassword()
{
    $this->assertFalse($this->factory->validates(['password' => null]));
}

Or we can just immediately pass the whole line to assertFalse. Now what about duplicates?

function testDoesNotAllowDuplicateUsernames()
{
    // save the user and return the model
    $username = $this->factory->create()->field('username');
    $user     = $this->factory->build(['username' => $username]);
    
    $this->assertFalse($user->validates());
}

Now, create() bypasses the validation process so there won't be any conflicts with the validation while testing.

create() and build() returns the class model so you can use model functions like field(), read() and even get objects like validationErrors. Play around with it.

Good Practice

I always put a test testHasAValidFactory() which looks like this:

function testHasAValidFactory()
{
    $_factory = $this->factory->build();

    $this->assertNotEmpty($_factory->save());
}

or sometimes

function testHasAValidFactory()
{
    $this->assertTrue($this->factory->validates());
}

just to be sure that my default factory runs and not mess things up.

Like the post? Check me out on Github or even share the post if it's kinda useful. :)