In an earlier post I asked what’s missing for testing a CakePHP application. In the meantime I created a test suite which implements the missing features mentioned in that post. In this article I will introduce this new test suite. (please notice that I don’t know yet whether this test suite will become the official test suite)

Ok, let us start with the installation, which is simple (I assume you already installed SimpleTest):

  • Download the test suite
  • Copy the content of the “vendors” folder from the zip to your global vendors folder
  • Copy the content of the “app” folder to your app folder
  • Define the database connection(s) for testing in app/config/test-database.php

That’s it.

The reason the test suite uses its own database configuration file is that it is more flexible than using the $test configuration predefined in database.php.

While copying the files you may have noticed the new directory structure for the tests:

app
  tests
    components
    controllers
    fixtures
    groups
    helpers
    models

The folders “components”, “controllers”, “helpers” and “models” are for the tests of the respective classes. The “fixtures” folder is for fixtures and “groups” for group tests.

Now let us write some tests to see the test suite in action.

We start with a test for a User model with just one method: isLoginAllowed.

// app/tests/models/user_test.php
class UserTest extends CakeTestCase {
    var $User = null;
    var $fixtures = array('Users');

    function setUp() {
        $this->User = new User();
    }

    function testIsLoginAllowed() {
        $this->assertTrue($this->User->isLoginAllowed($this->dho['username'], $this->dho['password']));
        $this->assertFalse($this->User->isLoginAllowed($this->dho['username'], ‘invalid’));
        $this->assertFalse($this->User->isLoginAllowed(’invalid’, $this->dho['password']));
    }
}

As you see our test extends CakeTestCase, the base class for all test cases. We have also to follow the naming convention for test cases: same name as the object under test plus “Test”, e.g. UserTest, UsersControllerTest. To make our testing job a bit easier we use a fixture:

// app/tests/fixtures/users.php
class Users {
    var $columns = array('id', 'username', 'password');
    var $dho = array(1, 'dho', 'geheim');
}

You probably know this fixture. It is the same I have used in the article about the Selenium test suite. This means you can reuse fixtures in your Selenium tests.

With that, we have written our first test, and now we can execute it. There exist different options how we can execute our test. We can use the command line:

// execute all tests
php bake2.php test myapp
// execute all model tests
php bake2.php test myapp models
// execute the tests for the User model
php bake2.php test myapp model User

or we can use the web interface (a simple wrapper for the command line):

// execute all tests
http://myapp.localhost/tests/all
// execute all model tests
http://myapp.localhost/tests/models
// execute the tests for the User model
http://myapp.localhost/tests/model/User

As we don’t have implemented anything we get as expected some failures. I omit the implementation of the User model, as it is not that interesting in this context.

Testing components and helpers is similar to testing models, so we can go directly to the testing of controllers. As it is rather difficult to unit test controllers due to all the dependencies a controller usually has, we will write functional tests.

In our case we will write tests for the “login” action of the “UsersController”. As this action can be called with both GET and POST requests, we have to write at least two tests. Let us start with the test for the GET request:

// app/tests/controllers/users_controller_test.php
class UsersControllerTest extends CakeTestCase {

    function testLogin() {
        $this->get('/users/login');
        $this->assertResponse(SUCCESS);
    }
}

This test is rather simple: we simulate a GET request and check whether the response was a SUCCESS. SUCCESS means there was no cake error or redirect.

More interesting are the tests for POST requests. We test two cases: a successful and a not successful login. If the login is successful, the user should be redirected to /projects and the user id must be stored in the session. In the other case a flash message should be shown without doing a redirect.

// app/tests/controllers/users_controller_test.php
class UsersControllerTest extends CakeTestCase {
    var $fixtures = array('Users');

    function testLogin() {
        $this->get('/users/login');
        $this->assertResponse(SUCCESS);
    }

    function testLoginValidUser() {
        $this->post('/users/login', array('User' => array('username' => $this->dho['username'], ‘password’ => $this->dho['password'])));
        $this->assertResponse(REDIRECT);
        $this->assertRedirectedTo(’/projects’);
        $this->assertEqual($this->dho['id'], $this->session['User.id']);
    }

    function testLoginInvalidUser() {
        $this->post(’/users/login’, array(’User’ => array(’username’ => ‘invalid’, ‘password’ => ‘invalid’)));
        $this->assertResponse(SUCCESS);
        $this->assertFlash(’Invalid username/password’);
    }
}

With that we have written our first controller test. We can execute it in the same way as we executed the model test.

Last, but not least, let us group the two tests we have written up to now in a group test:

// app/tests/groups/user_tests.php
class UserTests extends CakeGroupTest
{
    function UserTests()
    {
        $this->addTestFile('controllers'.DS.'users_controller_test.php');
        $this->addTestFile('models'.DS.'user_test.php');
    }
}

Our group test has to extend “CakeGroupTest”, but we are free to name it in any way we like. To execute our group test we have to use:

php bake2.php test myapp group UserTests

or

http://myapp.localhost/tests/group/UserTests

Ok, that’s it.

Happy testing :)