Friday 6 September 2013

Dealing with date function in test under CakePHP

When writing test, we often run into situations like this:
        

    public function testReceived()
    {
        // ....
        $signed = true;
        // order is a mocked object
        $this->Order->expects($this->once())
            ->method('save')
            ->with(array(
                'signed' => $signed
                'received' => date('Y-m-d H:i:s')
            ));
        $this->Order->received($signed);
    }

    public function received($signed)
    {
        $this->save(array(
            'signed' => $signed,
            'received' => date('Y-m-d H:i:s')
        ));
    }

The test looks fine and it will possibly pass. Why possibly? The tricky thing here is the two calls of function 'date' and there is undoubtedly time difference between them. Thus, if you have a really slow machine or your first call to date function is not in the same second as the second one or you put a sleep function to sleep 1 second or more before calling 'received' method in the test, your test will be guaranteed to fail.

You may think using the second parameter which is $delta in PHPUnit's equalTo method to add tolerance of 1 or 2 seconds so that any time difference between 1 or 2 seconds will be regarded equal. However, 'equalTo' method's $delta parameter doesn't work on date string or date string in the array despite the wishful thinking.

The only solution left is to create our own constraint and assertion.
Go here and download the code then copy it into your CakePHP project.
In your test, you should add code below at the top:
        
    App::uses('AppTestCase', 'Lib/TestSuite');

Change your test class to extend from 'AppTestCase' instead of 'CakeTestCase'.
Finally, use '$this->isArrayEqual' with second parameter as tolerance of number of seconds and use '$this->assertArrayEquals' with third parameter as tolerance of number of seconds.

The test code above will become:
        

    public function testReceived()
    {
        // ....
        $signed = true;
        // order is a mocked object
        $this->Order->expects($this->once())
            ->method('save')
            ->with($this->isArrayEqual(array(
                'signed' => $signed
                'received' => date('Y-m-d H:i:s')
            ), 2)); // any difference of date string within 
                    // 2 second in the array will be regarded as equal
        $this->Order->received($signed);
    }

Now, you don't need to worry about these date strings causing problems anymore.

Wednesday 21 August 2013

Run CakePHP test case in the PHPStorm

If you like me, using PHPStorm as your IDE for writing CakePHP programs, you would very much like running tests in it as well.

Download the file here and put it in your project.
In the menu, go to 'Run'->'Edit Configrations...'->expend 'Defaults' on the left in the window popped up->select 'PHPUnit'->put '-r path_to_cakeunit4phpstorm.php' in the 'interpreter options'->hit 'OK'.
Open one of your test file, put your caret on the name of the test class or above it. Press 'control-shift-r'.
If you haven't set up your PHP interpreter, you will see a window popped up with a 'fix' button on the right bottom. Click on it. Select 'PHP language leavel' and click on '...' after the dropdown for 'interpreter'. Another window will pop up. Click the plus sign and choose your interpreter. Then 'OK', 'OK', 'Run'.

If you want to run individual test method, put the caret on the method name or inside the method and press 'control-shift-r'.

Enjoy.

Friday 16 August 2013

Strategy of dealing with private methods in TDD for CakePHP

If you are writing, sooner or later you will run into question like 'how do I test private methods?' or 'What is the way to go about private methods in testing?'.

Let us explore two means of dealing with this issue. And I will use example in the context of CakePHP but I believe this applies to all PHP application and possibly other language as well.

An example

We have a model class called 'Post' which needs two methods named 'add' and 'edit'. They both send out email if a post is saved successfully, which means there will be a private method in the 'Post' class to handle this.

The first solution will be writing a private method in the test to mock up 'CakeEmail' and expects 'config' method is called on it with an array contains email configuration and data passed from the tests for 'edit' and 'add' methods and then 'send' method is expected to be called. It looks like this:
    public function testEdit()
    {
        ... // testing editing post and generate an $email
        $this->sendEmail($email);
    }
    
    public function testAdd()
    {
        ... // testing adding post and generate an $email
        $this->sendEmail($email);
    }
    
    private function sendEmail($email) {
        $this->Post->CakeEmail = $this->getMock('CakeEmail', array(
            'config',
            'send'
        ));

        $this->Post->CakeEmail->expects($this->once())
            ->method('config')
            ->with($email);

        $this->Post->CakeEmail->expects($this->once())
            ->method('send');
    }

This way we can clearly see that the code in 'Post' will be a private method in charge of sending email and 'edit' and 'add' both utilize this method to send email out.

The second solution is using 'Reflection' and 'runkit'. The idea is to use runkit to change 'sendEmail' method from 'private' to 'public' and in the tests for 'add' and 'edit' we mock up 'sendEmail' method so we can expect it is called. Then we write a test for 'sendEmail' mocking up 'CakeEmail' and using 'Reflection' to invoke 'sendEmail' in the test. This way we make sure the internal structure of 'Post' method is tested which means encapsulation is broken and any future change of the name of 'sendEmail' method will need change the test for it and involve it first. Thus, I don't recommend taking this approach.

Monday 5 August 2013

Test Driven Design(TDD) in CakePHP 2 Part 2

I have explained the way how TDD should be done in the CakePHP for controllers. In this post, I will go through how it is done in the models.

Let's again start with an example. If you read my last post, we need a method on the 'Cake' model named 'getNumberOfCakes' to take a integer and return the number of cakes in an array fetched from database.

If you baked the model, you will have a test for it baked under 'app/Test/Model/' named 'CakeTest.php'. You can use cake console to generate it as well. Just simply type 'Console/cake bake test' and follow the prompt. The test class looks like this:

App::uses('Cake', 'Model');

/**
 * Cake Test Case
 *
 */
class CakeTest extends CakeTestCase {

/**
 * Fixtures
 *
 * @var array
 */
    public $fixtures = array(
        'app.cake'
    );

/**
 * setUp method
 *
 * @return void
 */
    public function setUp() {
        parent::setUp();
        $this->Cake = ClassRegistry::init('Cake');
    }

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

        parent::tearDown();
    }

}

'setUp' and 'tearDown' methods are called before and after every test method runs respectively. 'setUp' generally instantiates a model object, in this case 'Cake' model, and assigns it to the test class itself with the name of model. Whereas, 'tearDown' unsets it.

You may notice that a public instance variable called 'fixtures'. It is an array contains the models being used in the tests which are using fixtures as data instead of real data already in the database. Again, if you bake the model, you will find a fixture under 'app/Test/Fixture' called 'CakeFixture'. Otherwise, you can bake the fixture using 'Console/cake bake fixture'. Open it, you will find two properties, which are 'fields' - the schema of 'Cake' table and 'records' - the data for that table. If you have set up test database in the database.php under 'app/Config', your 'Cake' model under the test class above will always be backed by a table defined by 'fields' property with data inside defined by 'records' property in the test database. This table will be truncated after running every test method in the test class and refilled before running them.

With the knowledge of fixtures, it couldn't be easier for us to write a test for model. In this case, we just need to call 'getNumberOfCakes' method on the '$this->Cake' and using '$this->assertEquals' method to make sure the result returned is the result we can find from the fixture. In order to test 'getNumberOfCakes' method, we need to have at least 2 records in the fixture. So we change the 'records' property on 'CakeFixture' to:

public $records = array(
    array(
        'id' => 1,
        'name' => 'Cheese Cake',
        'created' => '2013-06-23 15:59:45',
        'modified' => '2013-06-23 15:59:45'
    ),
    array(
        'id' => 2,
        'name' => 'Choc Mud Cake',
        'created' => '2013-06-25 10:58:00',
        'modified' => '2013-06-25 13:53:00'
    )
);

Then, we write a test for 'getNumberOfCakes' method:
// in CakeTest class under app/Test/Case/Model
/**
 * test getNumberOfCakes method
 *
 * @return void
 */
    public function testGetNumberOfCakes() {
        $expected = array(
            array(
                'Cake' => array(
                    'id' => 1,
                    'name' => 'Cheese Cake',
                    'modified' => '2013-06-23 15:59:45'
                )
            )
        );
        $this->assertEquals(
            $expected, 
            $this->Cake->getNumberOfCakes(1)
        );
    }

You can run the test in the console using 'Console/cake test app Model/Cake' and you will see a 'PDOException' thrown out. This is because 'getNumberOfCakes' method does not exist yet on the 'Cake' model. To rectify this, we need go on to the step 2 of TDD process "write the code to let the test pass". But this time we are going to go through step 2 and step 3 in our mind first. Imagine you put a public method 'getNumberOfCakes' under 'Cake' model taking a parameter '$number' and then you just copy the value of '$expected' from test as the return value of the method. This way the test is going to pass. However, there are duplications. To reduce them, we use a 'find' method with 'all' as first parameter and an array contains 'limit' 1 and 'fields' as second parameter to replace the return value. To further reduce the duplication, we replace the number 1 with the variable passed to it. Finally, we end up with:
/**
 * get number of cakes, given $number
 * 
 * @return array
 */
    public function getNumberOfCakes($number)
    {
        return $this->find('all', array(
            'fields' => array(
                'Cake.id',
                'Cake.name',
                'Cake.modified'
            ),
            'limit' => $number
        ));
    }

You may have some doubts or questions about the test in this example. That can be 'Is the only assertion sufficient? Should we test the situation that passing 2 to getNumberOfCakes? Why passing 1 is good/sufficient here?'. Let's take a close look at the test. The '$expected' variable is an array with a structure of what's coming out of "find('all')". Thus, it can be expected that a "find('all')" being in 'getNumberOfCakes' when reducing the duplication of returning the same value of '$expected' from 'getNumberOfCakes' is the simplest and easiest way. Also, since we only have two records of cakes in the fixture, we can't put 2 to 'getNumberOfCakes' in the test. If we did, the simplest and easiest way to reduce duplication is to NOT put 'limit' in the array of the second parameter of 'find' method in the 'getNumberOfCake' and that is certainly not we want. By putting number one here, we can be sure that it is enough to produce the correct code to cover the test. This is a typical example to show how important and useful that 'Reduce duplication in the simplest and easiest way to make test stay passed' is.

Let us now look at a more complicated example. We need to have a method sitting on the 'Cake' model called 'addAndNotify' which takes an array '$data' and a boolean '$notify'. What it dose is saving '$data' into database, then sends an email using email config 'notify' if '$notify' is true.

First, we start with a test to test the data passed to 'addAndNotify' method gets saved into database.
// in CakeTest.php under /app/Test/Case/Model
/**
 * test addAndNotify method saves cake to database
 *
 * @return void
 */
    public function testAddAndNotifySavesCakeToDatabase()
    {
        $latestId = $this->Cake->field('id', null, array('id' => 'DESC'));

        $this->Cake->id = 1;
        $result = $this->Cake->addAndNotify(array(
            'Cake' => array(
                'name' => 'Fruit Cake'
            )
        ));
        $this->assertInternalType('array', $results);
        $this->assertEquals('Fruit Cake', $this->Cake->field('name', array(
            'id' => $latestId + 1
        )));
    }

The Code with no duplication to let it pass is:
// in Cake Model
    public function addAndNotify($data)
    {
        $this->create();
        return $this->save($data);
    }
By looking at the test above, you may have several questions:
Q. The first line of the test getting the last 'id' from 'cakes' table in the test database. And we know that the data in the table is from 'CakeFixture' class. So why don't we just look into the fixture to get an id?
A. As your program grows, you may need to add test data to fixtures, which means the last id will change and this test will fail. Imagine you have dozens of tests rely on the last id in the fixture hard coded in the test and it is certainly not a easy solution for good maintainability of your tests.

Q. Why do we assign number 1 to the 'id' on the '$this->Cake'?
A. As we know, if there is an 'id' on the 'Model', CakePHP will do an 'update' instead of 'create' when you call 'save'. And the later is what we want. If we don't assign a value to id, a call to 'save' method is enough to let the test pass but when you calling 'addAndNotify' multiple times, the later one is going to overwrite the one saved before. Thus, assigning number 1 to 'id' here is to make sure 'create' method is called before 'save'.

Q. Do we need to test the situation that '$result' is false?
A. Yes and No. Yes because we need to know 'false' is returned if the method can't save the data. No, because we have already covered the situation of returning false. Since the easiest and simplest way to have the correct return value in the 'addAndNotify' method to let the test pass is to return the value coming out of 'save' method, which insures if the data can't be saved, 'false' is returned as desired. This is another good example of telling us that how important 'simplest and easiest' is when you write your code to cover your test.

Q. Why do we only test the field 'name' is equal to 'Fruit Cake' and we don't test the created and modified time?
A. If you write the code below to test the 'created' and 'modified' time

// under testAddAndNotifySavesCakeToDatabase method in CakeTest.php 
// in /app/Test/Case/Model
    $this->assertEquals(array(
        'Cake' => array(
            'id' => $latestId + 1,
            'name' => 'Fruit Cake',
            'created' => date('Y-m-d H:i:s'),
            'modified' => date('Y-m-d H:i:s')
        )
    ), $this->Cake->read(null, $latestId + 1));
and it passes when you run it, I can assure you it is not the right test. Because the time we generate using 'date' function is technically not the same as the time the data is saved into database, although they can be in the same second to let the test pass. This means under some circumstances such as you have a very slow computer or you turn on the sql log for every query to write to a very slow hard disk or you call 'sleep' function right before this assertion, you will see the test fail. Therefore, testing created and modified fields auto-generated by cake can not be easily done. In the meantime, it is not necessary since the test for this functionality belongs to the tests of 'save' method in the CakePHP core. 

We need to write a another test to make sure when passing true to the second parameter of 'addAndNotify' method, it will send an email. First, we are going to do some modification of the test above:
 
// in CakeTest.php under /app/Test/Case/Model
/**
 * test addAndNotify method saves cake to database
 *
 * returns the first parameter to addAndNotify and the result coming out of addAndNotify
 * @return array
 */
    public function testAddAndNotifySavesCakeToDatabase() {
        $latestId = $this->Cake->field('id', null, array('id' => 'DESC'));

        $this->Cake->id = 1;
        $cake = array(
            'Cake' => array(
                'name' => 'Fruit Cake'
            )
        );
        $results = $this->Cake->addAndNotify($cake);
        $this->assertInternalType('array', $results);
        $this->assertEquals('Fruit Cake', $this->Cake->field('name', array(
            'id' => $latestId + 1
        )));
        return compact('results', 'cake');
    }
We change the test so it returns the array we passed to and the value returned from 'addAndNotify'. Then we write the test below:
// in CakeTest.php under /app/Test/Case/Model
/**
 * test addAndNotify method sends email when notify is true
 *
 * @depends testAddAndNotifySavesCakeToDatabase
 * @return void
 */
    public function testAddAndNotifySendsEmailWhenNotifyIsTrue($data) {
        $this->Cake->CakeEmail = $this->getMock('CakeEmail', array(
            'send',
            'config'
        ));

        $this->Cake->CakeEmail->expects($this->once())
            ->method('config')
            ->with('default');

        $this->Cake->CakeEmail->expects($this->once())
            ->method('send')
            ->with(array(
                'data' => array(
                    'name' => $data['results']['Cake']['name']
                )
            ));

        $this->Cake->addAndNotify($data['cake'], true);
    }
In the comment of this test method, there is a '@depends' annotation, which means this test depends on 'testAddAndNotifySavesCakeToDatabase' method and it will not run unless 'testAddAndNotifySavesCakeToDatabase' passes. Also, this test takes a parameter. It will be filled of the value returned from the test it depends on.
In this test method, a mocked object of 'CakeEmail' gets created and 'send' and 'config' methods are expected to be called on it to set up 'CakeEmail' object and send email with saved 'Cake' data.This way, we don't need to worry about duplicating the test we did in 'testAddAndNotifySavesCakeToDatabase' or mocking up 'save' method which is some sort of duplication as well. This is all because '@depends' gives us the confidence that when 'testAddAndNotifySavesCakeToDatabase' passes we will have a working pair of input and output of 'addAndNotify' method.
To satisfy this test method, we changed the code of 'addAndNotify' method to:
// in Cake.php under /app/Model
    public function addAndNotify($data)
    {
        $this->create();
        $result = $this->save($data);

        $this->CakeEmail->config('default');
        $this->CakeEmail->send(array(
                'data' => array(
                    'name' => $result['Cake']['name']
                )
            ));

        return $result;
    }
This seems to be a straight forward solution to let the second test pass. However, when you run it with 'Console/cake test app Model/Cake --filter testAddAndNotify', you will see 'PHP Fatal error:  Call to a member function config() on a non-object'. This is because the first test we wrote does not set a 'CakeEmail' object onto 'Cake' model. It intended that way since it is only testing the saving part of the method. Thus, a further change to 'addAndNotify' is required.
// in Cake.php under /app/Model
    public function addAndNotify($data, $notify = false)
    {
        $this->create();
        $result = $this->save($data);

        if ($notify) {
            $this->CakeEmail->config('default');
            $this->CakeEmail->send(array(
                'data' => array(
                    'name' => $result['Cake']['name']
                )
            ));
        }
        return $result;
    }
Run the test again, it will pass and I can't see any duplication needs to be reduced and the code is simple and easy enough. So that's it? No! The problem comes up with the first line of the second test method which is assigning a mocked object to 'Cake' model. This means 'addAndNotify' method does not create this object by itself and this can be problematic and confusing as people who want to use this method expect it to take care of that.
To rectify this, we write another test to force 'Cake' model create 'CakeEmail' by itself.
// in CakeTest.php under /app/Test/Case/Model
/**
 * test __get method creates CakeEmail object
 * @return void
 */
    public function test__getCreatesCakeEmailObject()
    {
        $this->assertInstanceOf('CakeEmail', $this->Cake->CakeEmail);
        $this->assertIdentical(
            $this->Cake->CakeEmail,
            $this->Cake->__get('CakeEmail')
        );
        $this->assertEquals('name', $this->Cake->displayField);
    }
The first assertion here is to make sure that object of class 'CakeEmail' is instantiated when we need it on the 'Cake' model and the second one is to test this instantiation only happens once. As to the third assertion, it is to make sure '__get' method on the parent object is called(for this to work as intended, remove the display field property if it is assigned to 'name' or 'title').
The simplest and easiest way to let the pass test is:
// in Cake.php under /app/Model
/**
 * @param string $name
 * @return mixed
 */
    public function __get($name) {
        if ($name == 'CakeEmail' && !isset($this->CakeEmail)) {
            $this->CakeEmail = new CakeEmail();
        }
        return parent::__get($name);
    }
Now, we can say this is it. To recap, we did the following in the three tests above:
  1. We used fixture to help us create test environment
  2. We used mocked object to prevent sending email out
  3. We used '@depends' annotation to avoid duplication of test
  4. We learned writing test can lead to lazy instantiation
Stay tuned for my next post about more tips and tricks of doing TDD under CakePHP.

Thursday 11 July 2013

Test Driven Design(TDD) in CakePHP 2 Part 1

I'd like to share the way how I implement TDD in CakePHP (2.0 and up) in this post by going through examples of tests for controller, model and other items found commonly used in CakePHP. I will be explaining why the test should be written as is and giving a few tips and tricks along the way.

You need to be an experienced CakePHP programmer to understand this post.

So, first things first, what is TDD?

You can find all the information about it around internet and I won't be repeating them. To me, TDD is three actions which repeat themselves:
  1. Write a test first
  2. Write code to make it pass
  3. Reduce duplication in the simplest and easiest way to make test stay passed 
Allow me explain these three steps in a simple example.

Write a test

Let's say we need a function called add, which returns sum of two numbers passed in. What we do is we write a test for it first.

function testAdd() {
    if (add(1, 2) === 3) {
        echo 'pass';
    } else {
        echo 'fail';
    }
}

If you run this function,  obviously, you will get a fatal error saying method is not defined which means the test has failed. Thus, we need to make it pass by doing step 2.

Write code to make it pass

Now, what is the code that can make this test pass?
The first thing come to my mind is this:
function add($number1, $number2) {
    return 3;
}
If you run the testAdd function again, you will see 'pass'. But hold on. This is not right, what we need is a function to sum any two number, not just for numbers adds up to three. Yes, you are absolutely right, hereby, we need step 3:

Reduce duplication in the simplest and easiest way to make test stay passed

Let's break this step into two part:
  1. find duplication
  2. reduce it
The duplication here is referencing the duplication between the test code and the code being tested. And clearly, the number 3 is the duplication here.

After finding this duplication, we need to reduce it in a simplest and easiest way. In the meantime, make the test stay passed. How are we going to do that, you asked? -- By changing the number 3 to $number1 + $number2.

So the code after this step will become:
function add($number1, $number2) {
    return $number1 + $number2;
}
I would like to stop here and talk about 'the simplest and easiest way' a little bit more.

When the duplication is reduced in the add function, it can go million ways, such as
function add($number1, $number2) {
    if ($number1 == 1.5 && $number2 == 1.5) {
        return $number1 * 6 - $number2 * 4;
    } else {
        return $number1 + $number2
    }
}
The function will still return the desired result, there is no more duplication and the test will stay passed. However, it is harder to maintain because of the extra logic. Thus, during duplication reduction process, we should write the simplest and easiest code to make the test stay passed.

Now you know the three steps of TDD. And you need to remember them and remind yourself every time you perform a TDD.

PHPUnit

Before going to the details of writing tests in CakePHP, we need to know something about PHPUnit because it is the test library sitting behind CakePHP's test environment.

Basically, it is a framework to help you write test. So things like
if (add(1, 2) === 3) {
    echo 'pass';
} else {
    echo 'fail';
}
becomes
class AddTest extends PHPUnit_Framework_TestCase {
    public function testAdd() {
        $this->assertEquals(3, add(1,2), "1+2 should equal to 3");
    }
}
PHPUnit has many method like this to make it easier to write test, check out the website: http://phpunit.de/manual/current/en/index.html and install it on your machine.

Don't worry if you are not familiar with PHPUnit. This dosen't give you difficulty of continuing reading the rest of this post and you can always go back to the link above to find out things you need to know.

TDD in CakePHP

Finally, we are here. Let's start with a simple example again.

We need to build a page shows a list of cakes. And according to the design we got from our designer we have written a view with html and some php code to iterate a view variable named 'cakes'. We also have a table called cakes with fields 'id', 'name', 'created' and 'updated'. I assume that you have baked empty controller and model for this table in the console so that we don't need to create these files manually.

Apparentally , we need an 'index' action in the CakesController for this task. Do we just start writing code of it? No! we start with a test for it like the one below:
// in the CakesControllerTest.php under app/Test/Case/Controller
App::uses('CakesController', 'Controller');

/**
 * CakesController Test Case
 *
 */
class CakesControllerTest extends ControllerTestCase {

/**
 * testIndex method
 *
 * @return void
 */
    public function testIndex() {
        $this->testAction('/cakes', array(
            'method' => 'GET'
        ));
    }

}
This test can be run in the terminal by typing 'Console/cake test app Controller/CakesController' under app folder. When the test runs, something will happen behind the scene as if the url of '/cakes' is visited with a http GET request and this is because testAction method is called in this test. Then, en error will be incurred since the action 'index' matches the url '/cakes' does not exist yet.
To make the test pass, which is the second step of TDD, a method of 'index' sitting in the 'CakesController' will be perfect.
// in the CakesController.php 
public function index() {

}
If you run the test again, it will pass('OK (1 test, 0 assertions)' with green background if you are not under windows). And there is no need for step3 here, because there is no duplication and the code is simple and easy enough.

Now, as said before TDD is the repetition of those three steps, we need to write some more code in the test to ensure the list of cakes is retrieved and set as a view variable. To do that, we are basically testing the find method on the Cake model gets called with parameter 'all'. The question here is how we are going to test that a particular method(in this case 'find') is invoked and the parameter passed to the method is correct. The answer is to use 'generate' and 'expects' methods like below:
// in the CakesControllerTest.php under app/Test/Case/Controller
/**
 * test index method
 *
 * @return void
 */
public function testIndex()
{
    $this->generate('Cakes', array(
        'models' => array(
            'Cake' => array(
                'find'     
            )
        )
    ));   
 
    $this->controller->Cake->expects($this->once())
        ->method('find')
        ->with('all');

    $this->testAction('/cakes', array(
        'method' => 'GET'
    ));
}
After calling generate method like this, model 'Cake' will become a fake 'Cake' object (we call it mocked object) with a fake 'find' method (mocked method) and 'CakesController' is assigned to '$this->controller'. Then, you can expect the 'find' method is called once on 'Cake' model with parameter 'all', which is the three lines of code after.

Now, if you run the test again, the test will fail and tell you that 'find' method is expected to be called once but actually called zero times. The message is clear enough to tell us that we need to call 'find' method in the index method as below:
// in the CakesController.php 
public function index()
{
    $this->Cake->find('all');
}
Run the test again, you will see it pass. It is time to go to step1 again. To display the list, we need to make sure a variable 'named' cakes is set on the controller.
// in the CakesControllerTest.php under app/Test/Case/Controller
/**
 * test index method
 *
 * @return void
 */
public function testIndex()
{
    $this->generate('Cakes', array(
        'models' => array(
            'Cake' => array(
                'find'     
            )
        )
    ));

    $fakeCakes = 'some cakes';

    $this->controller->Cake->expects($this->once())
        ->method('find')
        ->with('all')
        ->will($this->returnValue($fakeCakes));

    $this->testAction('/cakes', array(
        'method' => 'GET'
    ));

    $this->assertEquals($fakeCakes, $this->vars['cakes']);
}
The line of calling method 'will' means when test runs the 'find' method in the controller will return a string 'some cakes'. Then, the last line is to make sure the view variable set in the 'index' method has a name of 'cakes' and value of 'some cakes'. You may be wondering why the 'vars' on the test class is the view variables on the 'CakesController' class. This is because after 'testAction' method finish running the code in the 'CakesController', it saves the view variables on the test class object. So $this->vars in the test is actually view vars from 'CakesController'. Another thing needs some attention here is the value of $fakeCakes. If we change it to some other string or even an array or an object, the test still works because it is just to ensure that what comes out of 'find' method is the same as what is in the view variables. The code in the 'CakesController' to make the test pass is:
// in the CakesController.php 
public function index()
{
    $this->Cake->find('all');
    $this->set('cakes', 'some cakes');
}
If you run the test now, it will pass. But this code is not right. And how can we correct it? Easy! We do step3 'Reduce duplication in the simplest and easiest way to make test stay passed'. As we can see, the duplications between test code and controller code are the call of method 'find' with parameter 'all', string 'cakes' and string 'some cakes'. The method call can not be possibly reduced, so it stays there; the string 'cakes' can not be reduced either since it needs to be the name of the view variable. That leaves us the string 'some cakes'. Because the return value of 'find' method is expected to be 'some cakes', which is the same we put as the second parameter for 'set' method, the duplication can be reduced by passing the return value of 'find' method to the place that 'some cakes' is and without much thinking this is the simplest and easily way to reduce duplication.
// in the CakesController.php 
public function index()
{
    $this->set('cakes', $this->Cake->find('all'));
}
If you run the test again, it will stay passed. Since there is no more test to write for this functionality, it just simply indicates that there is no more code needed in the 'index' method.

As time goes by, new design comes along. This time in the 'index' method we created, we need to limit the number of cakes displayed to 5 if user is not logged in. Otherwise, limit them to 10. And we don't need the created date anymore in the view.
To fulfill the task above, we are going to change the test. First, we are going to test the situation that user has not logged in.
// in the CakesControllerTest.php
public function testIndexRetrievesFiveCakesWhenUserNotLoggedIn() {
    $this->generate('Cakes', array(
        'models' => array(
            'Cake' => array(
                'find'     
            )
        ),
        'components' => array(
            'Auth' => array(
                'user',
                'startup',
                'shutdown'
            )
        )
    ));

    $this->controller->Auth->staticExpects($this->once())
        ->method('user')
        ->with('id')
        ->will($this->returnValue(null));

    $fakeCakes = 'some cakes';

    $this->controller->Cake->expects($this->once())
        ->method('find')
        ->with('all', array(
            'fields' => array(
                'Cake.id',
                'Cake.name',
                'Cake.modified'
            ),
            'limit' => 5
        ))
        ->will($this->returnValue($fakeCakes));

    $this->testAction('cakes', array(
        'method' => 'GET'
    ));
    $this->assertEquals($fakeCakes, $this->vars['cakes']);
}
First, we changed the method name to indicate what it is testing. In addition, 'components' array has been added to 'generate' method call. This is to mock up 'user' method on the 'Auth' component so that during the test if there is any code in the 'index' action calls this method, it will not return the value in the session, instead it is going to return the value we set in the test, which has been set up by the code after the 'generate' method call. You may find out, there is a slight difference here, which is the call to 'staticExpects' method. Because 'user' method is a static method on AuthComponent, 'expects' method here will not work, instead we use 'staticExpects'. Of course you may say that we can use 'loggedIn' method instead of 'user'. I fully agree to that, but that method doesn't give me the chance of explain the usage of 'staticExpects'.

As to the 'startup' and 'shutdown' being here is because that 'user' method is called in these two methods during the request cycle and we don't want them to interfere the assertion that 'user' method on the AuthComponent only being called once in the 'index' method(this is needed for cakephp 2.3.6 and up).

Let us stop here for a moment and think about the meaning of this test. When the test run, 'index' method in the CakesController will be hit; 'user' method on 'Auth' component needs to be called once, its parameter will be 'id' and then return 'null'; 'find' method on the model 'Cake' also needs to be called,  its parameter will no longer be just 'all' but with the same second parameter we pass to 'with' method in the test and return value 'some cakes'. After the execution of controller code, we assert that view variable 'cake' has been set on the 'CakesController' with value 'some cakes', which is the same coming out of 'find' method.

The code to let this test pass is:
// in the CakesController.php 
    public function index() {
        $this->Auth->user('id');
        $cakes = $this->Cake->find('all', array(
            'fields' => array(
                'Cake.id',
                'Cake.name',
                'Cake.modified'
            ),
            'limit' => 5
        ));
        $this->set('cakes', $cakes);
    }
All the duplication can not be reduced further.
Then, we start another test to make sure when 'user' method returns not null value, limit will be set to 10 instead of 5.
// in the CakesControllerTest.php
    public function testIndexRetrievesTenCakesWhenUserLoggedIn()
    {
        $this->generate('Cakes', array(
            'models' => array(
                'Cake' => array(
                    'find'     
                )
            ),
            'components' => array(
                'Auth' => array(
                    'user',
                    'startup',
                    'shutdown'
                )
            )
        ));

        $this->controller->Auth->staticExpects($this->once())
            ->method('user')
            ->with('id')
            ->will($this->returnValue(1));

        $this->controller->Cake->expects($this->once())
            ->method('find')
            ->with('all', array(
                'fields' => array(
                    'Cake.id',
                    'Cake.name',
                    'Cake.modified'
                ),
                'limit' => 10
            ));

        $this->testAction('/cakes', array(
            'method' => 'GET'
        ));
    }
The difference between this test and the test before is that now 'user' method returns a value 1 and 'find' method has 'limit' of 10. And to make this more clear we can organize these two tests in the following way:
// in the CakesControllerTest.php
/**
 * index method shared by 
 * testIndexRetrievesFiveCakesWhenUserNotLoggedIn and 
 * testIndexRetrievesTenCakesWhenUserLoggedIn
 *
 *
 * @param integer $userId
 * @param integer $limit
 * @return void
 */
    private function index($userId, $limit) {
        $this->generate('Cakes', array(
            'models' => array(
                'Cake' => array(
                    'find'     
                )
            ),
            'components' => array(
                'Auth' => array(
                    'user',
                    'startup',
                    'shutdown'
                )
            )
        ));

        $this->controller->Auth->staticExpects($this->once())
            ->method('user')
            ->with('id')
            ->will($this->returnValue($userId));

        $fakeCakes = 'some cakes';

        $this->controller->Cake->expects($this->once())
            ->method('find')
            ->with('all', array(
                'fields' => array(
                    'Cake.id',
                    'Cake.name',
                    'Cake.modified'
                ),
                'limit' => $limit
            ))
            ->will($this->returnValue($fakeCakes));

        $this->testAction('/cakes', array(
            'method' => 'GET'
        ));
        $this->assertEquals($fakeCakes, $this->vars['cakes']);
    }
/**
 * testIndex method
 *
 * @return void
 */
    public function testIndex() {
        $this->index(null, 5);
    }

/**
 * test index method with logged in user
 *
 * @return void
 */
    public function testIndexWithUserLoggedIn()
    {
        $this->index(1, 10);
    }
The reason of doing this is that, by keep the code shared by the two tests, it is easier to maintain the test code. Also, it is more clear that the user id coming out of 'Auth->user' has a direct impact on the limit. So, the code let these two tests pass is:
// in the CakesController.php 
    public function index() {
        $cakes = $this->Cake->find('all', array(
            'fields' => array(
                'Cake.id',
                'Cake.name',
                'Cake.modified'
            ),
            'limit' => $this->Auth->user('id') ? 10 : 5
        ));
        $this->set('cakes', $cakes);
    }
When you run the test, it will pass. Again, the duplication between the test and the code can not be reduced.

So far, we have used tests for controllers to ensure the functional structure of the controller code. However, we are still not sure the code we have written in the controller to let these tests pass is 100% correct without open a browser and navigate to 'you_local_domain/cakes'. For example, if we change the line "->method('user')" to "->method('users')" and calling 'users' instead of 'user' on Auth component in the 'index' method, the test will still pass but when the real request comes in, the application will crash with error.

Of course, we can not guarantee the test we wrote is 100% correct. That's why we require a certain level of familiarity of the framework before you can start on test driven design. Plus, there are some tricks to reduce the possibility of composing an incorrect test. Putting code shared by tests testing same action into one private method like the last example is one. Reducing the code we put in the test is another one I am going to talk about.

To make a mistake in writing code, you need to start writing code first. Therefore, reducing the code you write is a way to reduce the possibility of making mistakes in writing code. Still using the tests we wrote above as an example, we can change the private 'index' method as followed:
    private function index($userId, $limit) {
        $this->generate('Cakes', array(
            'models' => array(
                'Cake' => array(
                    'getNumberOfCakes'     
                )
            ),
            'components' => array(
                'Auth' => array(
                    'user',
                    'startup',
                    'shutdown'
                )
            )
        ));

        $this->controller->Auth->staticExpects($this->once())
            ->method('user')
            ->with('id')
            ->will($this->returnValue($userId));

        $fakeCakes = 'some cakes';

        $this->controller->Cake->expects($this->once())
            ->method('getNumberOfCakes')
            ->with($limit)
            ->will($this->returnValue($fakeCakes));

        $this->testAction('/cakes', array(
            'method' => 'GET'
        ));
        $this->assertEquals($fakeCakes, $this->vars['cakes']);
    }
This test uses a method 'getNumberOfCakes' which takes $limit as parameter to replace 'find' method, which reduce the amount the code of specifying what fields needs to be retrieved and what type of 'find' method needs to be called and therefore leaves us less chance to make a mistake here. The code to let this test pass is:
    public function index() {
        $cakes = $this->Cake->getNumberOfCakes(
            $this->Auth->user('id') ? 10 : 5
        );
        $this->set('cakes', $cakes);
    }

Again, the duplication here can not be reduced. Now, we only need to ensure that method 'getNumberOfCakes' returns the correct results and that can be done in the tests of 'Cake' model. Whereas before, we have no way to ensure the results coming out of 'find' is really what we need since 'find' method is a CakePHP method and writing a test for it will not be test driven design. Also the code in the controller is less then before, which conforms to the 'fat model slim controller' principal in MVC pattern. This is a typical example of reducing code in controller tests leading to a slim controller.

Up to now, we have learned the basics of writing test for controller. And I am going to write a few tips here to help you solve some problem further down the way.

Exceptions

Some time you wanna assert that under some situation an action will throw out an exception. This can be done by adding an annotation to documentation before method declaration.
/**
 * @expectedException MethodNotAllowedException
 */
public function testIndex()
{
    $this->testAction('/cakes/');
}
This code means 'index' method is supposed to throw out an 'MethodNotAllowed' exception. The extensive usage can be found here.

Use most specific assertions

As the code going more and more complicated, you will find it is harder to test only use $this->assertEquals(). You can go here to find all kinds of assertion method to suit your needs.

Test certainty out of randomness

If your test involving a limited randomness, you need to test the certainty out of the randomness. Let's assume you want to test a method named 'getNumberOfCakes' on Cake model to take a random number between 10 and 100 in your action. The randomness is obviously the integer passed to 'getNumberOfCakes' and the certainty is that this number is between 10 and 100. So you can write:
// in the test method of test class extending ControllerTestcase
$this->controller->Cake->expects($this->once())
    ->method('getNumberOfCakes')
    ->with($this->logicalAnd(
        $this->greaterThan(10), 
        $this->smallerThan(100)
    ));
This code puts constrains of larger than 10 and smaller than 100 on the first parameter passed to 'findNumberOfCakes'.

Expects the times that methods gets called

In the example before, we use $this->once() to assert that method needs to be called only once. You can also use $this->any(), $this->never(), $this->atLeastOnce() and $this->exactly to suit your purpose. In addition, you can use $this->at($index) to assert the first, second or third... time a method to be called. You can use $this->onConsecutiveCall($value1, $value2, $value3 ....) in the $this->will to let a method return a series of results.

Always mock up beforeFilter

// in test methods of test classes extending ControllerTestCase 
       $this->generate('Cakes', array(
            'methods' => array(
                'beforeFilter'
            )
       ));
Add 'methods' key to 'generate' method's second parameter will give you the ability to obtain a mocked method on the controller. When tests run, the real 'beforeFilter' method will no longer be called. This makes your test more focused on the code in the action instead of repeating the test of what is going on in the 'beforeFilter' for every actions in the controller. For instance, you want to add some code to the 'beforeFilter' method of 'AppController' which involves calling method 'user' on the 'Auth' component. After you do that, you will find the test for 'index' method in 'CakesControllerTest' become failed because we only expect 'user' method to be called once on 'Auth' component. Some will say that we can change '->once()' to '->at(1)'. But imagine you have written 50 tests like this, and one more line of calling '$this->Auth->user' in the 'beforeFilter' will make you change '->at(1)' to '->at(2)' all over the tests, which means mocking up 'beforeFilter' leads to better maintainability of tests.

$this->getMock() and $this->getMockForModel()

Sometimes, you need a mocked object which can not be obtained by calling '$this->generate()' method. You can turn to using 'getMock' method. It is a very powerful method but really simple to use by passing the class name you want to mock up and the methods you want to mock up to it as the first and the second parameters. So we can do something like this:
public function testSomeMethod()
{
    $this->generate('Cakes', array(
        'methods' => array(
            'getEventManager'
        )
    ));
    $EventManager = $this->getMock('CakeEventManager', array(
        'dispatch' 
    ));
    $this->controller->expects($this->any())
        ->method('getEventManager')
        ->will($this->returnValue($EventManager));

    $EventManager->expects($this->once())
        ->method('dispatch')
        ->with($this->isInstanceOf('CakeEvent'));
    $this->testAction('/cakes/some_method');
}
This is to expect that 'getEventManager' gets called and returns a mocked object of 'CakeEventManager' with mocked method 'dispatch' and then we can use the object to expect that 'dispatch' method gets called with instance of 'CakeEvent'.
As to 'getMockForModel' method, it is a wrapper method of 'getMock' in order to pass configuration as the third parameter to 'getMock' to instantiate a mock model object and then put it in the 'ClassRegistry' so that next time you use it through '$this->loadModel()' in controller or 'ClassRegistry::init()' anywhere else you get a mocked model object. To use it, just simply call it with model name and an array of methods you want to mock up and it will return a mocked model object. Then you can call 'expects' method on it like we do with other mocked object.

The request object on $this->controller

'$this->controller->request' is a mocked object. However, if you call 'expects' on it, you will not get desired result or behavior even if you get a mocked 'CakeRequest' using 'getMock' and assign it to '$this->controller'. This may sound confusing. So let me explain it. When you call 'testAction' in the test method, an object of 'CakeRequest' will be instantiated and assigned to '$this->controller'. So before calling 'testAction' method, '$this->controller->request' is not a mocked object. And if you assign a mocked 'CakeRequest' to it and do '$this->controller->request->expects(...)...', it will not work since the '$this->controller->request' will be replaced with a new object in the 'testAction'. Because 'expects' must be called on mocked object before 'testAction' trigger the test, there is just not a simple way of calling 'expects' on '$this->controller->request'.

Simulating AJAX request

Now we know we can't do something like '$this->controller->request->expects...' to expect 'is' method is called on request object with 'ajax' as parameter and returns 'true'. So how do we simulating a ajax request? Easy! Just set $_SERVER['HTTP_X_REQUESTED_WITH'] to 'XMLHttpRequest' before calling 'testAction' and don't forget to unset it after 'testAction'.

The sequence of models passed to generate 

When action gets complicated, you may want more than one mocked model through 'generate' method. And it is very important that you put them in right order. Let's say you want to mock up two models named 'Account' and 'User'. If you put them under 'models' key to 'generate' method in the order of 'Account' and 'User', what happens first inside 'generate' method is to get a mocked object of 'Account', which triggers the '__construct' method on the 'Model' class to go through all the relationship set up in 'Account' model and create object of related models onto itself(if they are not in the class registry). Thus, if you have a relationship set up on 'Account' says 'hasMany' 'User', you will have a 'User' object sitting on 'Account' model after this point. Then the 'generate' method moves to the next model you passed to it, which is 'User' and put the mocked object of it in the class registry. Finally, when you want to do '$this->controller->Account->User->expects...' in your test, you will get an error since the 'User' here is not the one in the class registry that 'generate' method puts in but a real 'User' object created from '__contract' method of 'Model'.

Run test individually

Some times, running all tests in one controller can take some time. There is a way to only run some of them. Using '--filter' like this 'Console/cake test app controller/UsersController --filter testIndex' runs tests in the 'UsersController' starting with 'testIndex'. So if you have a test named 'testIndexThrowException', it will run as well.

--stderr option

 If you use 'CakeSession' in your test, you will have some trouble. Appending '--stderr' in the command line will solve this problem.


Thank you for reading this post and I will be adding more tricks and tips here to help you write test for controllers. Comments and criticism are welcomed.

In the meantime, stay tuned for the next part of this post about test for models.