Testing with CakePHP 1.2 – A preview

Published on December 18, 2006 and tagged with cakephp  testing  tutorial

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 :)

59 comments baked

  • ryan December 19, 2006 at 20:11

    looks interesting; i’ve never done any app testing but i’m thinking of giving this a shot (it’s probably a smart thing to do)

    btw, part 3 of IBM’s developerWorks series on Cake is up:
    http://www-128.ibm.com/developerworks/opensource/library/os-php-cake3/

  • cakebaker December 20, 2006 at 09:12

    @ryan: I can only recommend to test your applications. But I am also aware that testing is not the right thing for everyone. So give it a try and look if it works for you. Good luck!

  • Martin March 11, 2007 at 21:16

    Bake2 will not work with non standard webroot and app paths :(

    Is there any chance to do that ?

  • cakebaker March 12, 2007 at 12:26

    @Martin: What do you try to accomplish? And what is the problem?

    Personally I use it without problems with an advanced setup of CakePHP.

  • Martin March 12, 2007 at 13:36

    My structure of folders is:
    app
    lib/cake
    webroot

    all works without problems but bake2 tasks not working, problem with paths, when trying to load configs etc..

    please help :)

  • cakebaker March 12, 2007 at 18:37

    @Martin: Please be more specific. What command are you using? What errors do you get? Provide as much information as possible.

  • Martin March 12, 2007 at 20:28

    Ok, Ive downloaded last revision and last simpletest and it starts working, but : Is there any chance bake2 return error code ? Im using phing for building and execute task require returncode if test fail.

    thanks for help

  • cakebaker March 14, 2007 at 19:45

    @Martin: No, there is no return code.

  • cakebaker » How to use the official CakePHP test suite March 23, 2007 at 16:55

    [...] I still prefer my own test suite, even though it requires a bit more configuration [...]

  • hydra12 March 26, 2007 at 20:35

    Will this work with on a plugin? I’m developing a plugin called auth with a controller AuthLogin. I dropped the test suite into the plugin controller, but it says it can’t find the config/test-database.php file. Any ideas?

  • hydra12 March 26, 2007 at 22:53

    OK, more info. The problem seems to be with test_task.php and the CONFIGS constant. If I call my controller, the CONFIGS is C:\web\projects\transportation\config\, which is correct. When test_task is called, CONFIGS shows as C:\web\projects\cake_1.2.0.4451alpha\\app\config\, which is wrong. I can’t figure out why it is doing this or how to fix it. Any ideas?

  • hydra12 March 27, 2007 at 16:07

    OK, I solved it. My app didn’t have an entry in app.ini, so the test task couldn’t resolve aliases and paths.

  • cakebaker March 27, 2007 at 19:06

    @hydra12: Good to hear you could solve the problem in the meantime :)

  • krishnan April 19, 2007 at 01:25

    I am running into some issues when there is a foreign Key constraint between the tables? I have a users Table and profiles table with a fk constraint in profiles table for the user_id.

    When I run the tests, the fixtures load correctly but it appears they also run the delete command which fails on users because of the constraint in profiles table

  • krishnan April 19, 2007 at 08:54

    in the __loadFixtures function the order in which the inserts are done is the same as the order in which the deletes are done. It should be the reverse order for the deletes

    one way to fix is by doing the deletes for loop separately

    or the other way is to have an unloadFixtures function
    with something like …

    foreach (array_reverse($this->fixtures) as $fixture){
    $f = new $fixture;
    ……

  • krishnan April 19, 2007 at 08:55

    the order of deletes shoudl be the reverse of the inserts in the __loadfixtures function…

  • cakebaker April 20, 2007 at 17:38

    @krishnan: Thanks for your comments, I will fix this issue.

  • krishnan May 17, 2007 at 02:16

    A couple of other changes I have had to make – a) set the path to include the app root ( like cake index.php) b) also provide an option while dispatching the url to not render the view

  • cakebaker May 19, 2007 at 09:42

    @krishnan: Hm, why do you have to set such a path? And what do you mean with point b), I don’t understand it…

  • cakebaker » New versions of test suite and coretest script June 01, 2007 at 10:53

    [...] That’s it. You can download the new version from the downloads section. If you want to know how to use the test suite, have a look at the introduction. [...]

  • Lars December 17, 2007 at 14:45

    Hi,

    i can’t figure out what i have to put in the apps.ini?
    Always get the message:
    No entry in vendors/testsuite/apps.ini for controllers

    Can someone help me out?

  • cakebaker December 17, 2007 at 19:14

    @Lars: It contains an alias as key plus a path to the app folder of your application, for example:

    noserub = /home/dho/projects/noserub/app
    

    HTH

  • one_cart December 21, 2007 at 09:57

    How use command line

  • cakebaker December 22, 2007 at 19:08

    @one_cart: With the current version you can use it like:

    php test.php your-app-alias
    

    where “your-app-alias” is the key in apps.ini.

    HTH

  • Matt C January 08, 2008 at 22:52

    CAKE_CORE_INCLUDE_PATH is incorrect in test.php?
    My CLI and web interface both yield

    Fatal error: Class ‘App’ not found in /opt/lampp/htdocs/cake_1.2.0.6311-beta/cake/libs/controller/controller.php on line 30

  • Matt C January 08, 2008 at 22:54

    CAKE_CORE_INCLUDE_PATH is incorrect in test.php?
    My CLI and web interface both yield

    Fatal error: Class ‘App’ not found in /opt/lampp/htdocs/cake_1.2.0.6311-beta/cake/libs/controller/controller.php on line 30

    Also: your website is frequently unavailable. Is it, um, built with CakePHP ? ;)

  • cakebaker January 10, 2008 at 19:59

    @Matt: I have to prepare a new release for cake 1.2beta.

    And no, this blog is not built with CakePHP, it uses WordPress, but I think it is more a problem of the hoster than a software problem ;-)

  • Luke Visinoni January 17, 2008 at 02:30

    Is this still current? Is this still the best way to test cakephp apps?

  • cakebaker January 18, 2008 at 18:06

    @Luke: The version in the download section doesn’t work with the beta version of cake. I have to upload a new version, which will probably happen this weekend.

    I don’t know whether it is the best way to test cakephp apps, as I have no experience using the built-in test suite.

  • Blog Dot Php With cakePHP » Полезные ссылки по cakePHP January 25, 2008 at 21:51

    [...] О тестах в CakePHP + инструкция по тестированию [...]

  • Bruno January 31, 2008 at 19:11

    The tutorial can be better. I lost a long time to find out what he alone does not explain. It wants to be clearer and deeper into explanations. Not all people dominate the CakePHP.

  • cakebaker February 02, 2008 at 10:39

    @Bruno: What do you think could be better? Where did you have problems?

  • Jahangir February 08, 2008 at 15:28

    I have do the same to run my test for cake php but when I run on model test from http://localhost/sch/tests/ ..the screen completly white with nothing on screen..as well as source of that page…

    Any Idea where I am wrong…

    Thanks.

    Regards,

  • abstract March 05, 2008 at 09:34

    Tutorial is great. Thx.
    I found two bugs in Your code.
    i use prefix in DATABASE_CONFIG, so I added to
    cake_test_case.php in the beginning of function checkForColumnAvailability:
    if (!empty($db->config['prefix']))
    {
    $tableName = $db->config['prefix'].$tableName;
    }
    Secondly, i modified file test_task . In function injectTestableController. I changed line
    $appControllerContent = file_get_contents(CAKE.$appController);
    to:
    $appControllerContent = file_get_contents(CAKE . ‘libs’.DS.’controller’.DS.$appController);
    It helps if You don’t use Your own app_controller.php :)

  • cakebaker March 06, 2008 at 17:44

    @abstract: Thanks for the fixes! I applied them and uploaded a new version.

  • Rich Yumul March 14, 2008 at 01:39

    Thanks for the great work with the testsuite! I’ve been looking for an elegant way to test controllers. Is there a way to check on how the controller actions affect the database, on top of the type of response received? How do you recommend checking data from the database?

    For example, if your controller action updates a column in the database from 4 -> 5, how do you recommend checking that value?

    Thanks! Good Stuff! :)

  • Rich Yumul March 14, 2008 at 02:21

    Sorry if I was a little repetitive in my last post… :) anyway, is there a way to access the response (the rendered view, raw HTML, etc.) from the action tested?

    Thanks again!

  • Lucho March 14, 2008 at 16:22

    This might be a silly question (blush) but I’m stuck: What is bake2.php? Where can I find it? Did I miss something?

    I have cake 1.2 and SimpleTest 1.0.1beta2.

    Thanks for the suite, it looks great!

  • Rich Yumul March 14, 2008 at 21:03

    I resolved my issue. I did a little research and found you can access the controller thru $this->controller. It allows you to query the underlying database like $this->controller->Model->findById(3), for example.

    You can access the vars set in the view via $this->controller->viewVars.

    I haven’t figured out how to access the rendered HTML yet, but then again, I think it makes sense, from the controller tier, you shouldn’t be concerned with stuff in the view.

    Also another nice feature that this testsuite has over the one that is distributed with cake is that it can handle redirects. With the cake test suite, test cases for actions that result in a redirect, the test results are not output.

    Thanks!

  • cakebaker March 15, 2008 at 18:23

    @Rich, Lucho: Thanks for your comments!

    @Rich: Yes, that’s the way I would do it. But I usually put as much as possible into the models and test then the models instead of the controllers.

    @Lucho: It is not a silly question ;-) bake2.php was the predecessor of the current console, and no longer exists. Now you have to use:

    ~/folder/with/testsuite$ php test.php app-alias
    

    I have to update the documentation as it is a bit outdated. Hope that helps in the meantime.

  • Rich Yumul March 20, 2008 at 01:03

    I’ve been thrashing around with CakePHP, XDebug, ZendDebug, Eclipse, PDT, PHPEclipse, ZendDebugger for Eclipse, and SimpleTest, trying to figure out how to debug my CakePHP Unit Tests.

    I finally found a working solution using Eclipse, PDT, ZendDebugger (for the server), using dhofstet’s testsuite.

    If you’re interested, you can read about my odyssey here: http://swelldog.com/node/15

  • cakebaker March 20, 2008 at 17:58

    @Rich: Thanks for the link!

  • Bhushan March 24, 2008 at 14:33

    I have given path as “http://xyz.com/test.php”. But its giving error like “#!/usr/bin/php -q No entry in vendors/testsuite/apps.ini for”.

    I have put “simpletest” and “testsuite” folder in app/vendors. and test.php in webroot. I have placed tests folder in app folder.
    Can anybody help me?

  • Bhushan March 24, 2008 at 15:18

    I am getting this error- “#!/usr/bin/php -q No entry in vendors/testsuite/apps.ini for Notice: Use of undefined constant APP_PATH – assumed ‘APP_PATH’ in /opt/decktrade/decktrade/current/src/webapp/www/app/webroot/test.php on line 185 Notice: Use of undefined constant VENDORS – assumed ‘VENDORS’ in /opt/decktrade/decktrade/current/src/webapp/www/app/webroot/test.php on line 122 Task not found: test”? whats the problem .. can anybody help me?

  • cakebaker March 25, 2008 at 17:33

    @Bhushan: Hm, did you add an application alias with the path to the app folder to apps.ini? Something like:

    noserub = /home/dho/projects/noserub/app
    

    Hope that helps.

  • Bhushan March 27, 2008 at 08:49

    What is application alias? i think i have problem with cakephp version. i am using cakephp 1.1 version. And this tutorial is for version 1.2. How can i achieve this in cakephp 1.1 version. Please help.

  • cakebaker March 27, 2008 at 18:34

    @Bhushan: For CakePHP 1.1 you have to use the test suite from cakeforge. But please be aware it doesn’t work in the way described here…

  • Richard Yumul April 12, 2008 at 02:39

    How do you test components so that the other components that it relies on (such as Session) are available? If I try to use ‘Session’ in my custom component, when running a testcase, I get an error like

    Fatal error: Call to a member function write() on a non-object in /home/rmy/htdocs/sfl/app/controllers/components/cart.php on line 9

    When I call $this->Session->write(‘foo’, ‘bar’).

    Is there a particular way to instantiate a component in a TestCase so that the other components it relies on are available?

    TIA!

  • Irina Dumitrascu April 12, 2008 at 14:01

    Hi,

    thanks for providing this library, we use it happily in our current projects as we encountered a blocking problem with the beta official one (http://bakery.cakephp.org/articles/view/testing-models-with-cakephp-1-2-test-suite#comment-2166)

    For those using the library for the first time, here are some things that we found through practice (and debugging :D):
    - the order of the fixtures that you add in test_model, test_controller matters – put first the ones that do not depend on others, and last the ones that depend on others (foreign keys).
    Example: if you have tags, posts, and posts_tags (that is a has and belongs to many relation between posts and tags – HABTM), import first the fixtures for posts and tags, and put posts_tags in the end, something like:
    var $fixtures = array(‘Posts’,'Tags’,'PostsTags’);

    - obvious one (but not easy to spot in the beginning): the conditions (not-nulls,bad type,uniques, foreign keys) defined in the DB must be respected (of course :D) in the data set in the fixtures. If your data does not respect it, it will not be inserted (silently).

    In order to make it “cry and die” if the data cannot be inserted, go to vendors/testsuite/cake_test_case.php and add in the function loadFixtures, in the end after
    $sql = ‘INSERT INTO ‘.Inflector::underscore($fixture). ….

    $ok = $db->execute($sql);
    if (!$ok) {
    print(“\n” . ‘There have been some errors while ‘.
    ‘loading fixtures, aborting as the tests will ‘.
    “not work.\n\n The error is: ” . $db->error . “\n”);
    die();
    }

    - when you create a test for a controller, and use the methods get and post with data to send, the array that you pass to the function is that one that you want to have in the controller as $this->data, not in http post form
    correct:
    array(
    ‘Post’=>array(‘title’ => ‘x’, ‘contents’ => ‘y’)
    )
    while in the beginning we tried passing the data like
    array( ‘data[Post][title] => ‘x’, ‘data[Post][contents]‘ => ‘y’),
    that mimicked the way variables are sent through HTTP for real life usage.

  • Irina Dumitrascu April 12, 2008 at 14:33

    Hi,

    we’ve updated the code in order to be able to put more than one record/fixture in the db. Here is the patch, if you would like to integrate it:
    http://www.geocities.com/irina2000d/cake_test_case.patch.txt

    It contains also the ‘cry and die’ fix from the previous post :)

  • Irina Dumitrascu April 12, 2008 at 16:05

    How we mocked our components (would qualify as a slightly-soiled hack :) )

    Another thing we needed for testing was to mock some components. We wanted to bypass the authentication and use a simple, fake authentication component that would pass as authenticated the user we wanted.

    In order to do that we:
    - hijacked cake’s dispatcher class, separating the fragment that initializes the controller and starts it in a function
    - in testsuite’s test_dispatcher class the function was overridden, and we:
    - added the FakeAuth component to the set of components of the controller
    - started the controller
    - modified the pointer to the Auth component to point to our FakeAuth object, that was created when the controller was started

    Please let me know if there is any less intrusive way to do this.

    http://www.geocities.com/irina2000d/dispatcher.patch.txt
    http://www.geocities.com/irina2000d/test_dispatcher.patch.txt

  • cakebaker April 13, 2008 at 18:48

    @Richard: You have to instantiate the component and set it as property of your component, for example:

    public function setUp() {
        $this->myComponent = new MyComponent();
        $this->myComponent->Session = new SessionComponent();
    }
    

    @Irina: Thanks for your explanations! I will add the snippet to the code as it makes sense to show the errors.

    Regarding the post and get methods, do you think it would be better if the data have to be passed in the form “data[Model][field]“?

  • Noi @ Dream Production » Blog Archive » Lista.lu - de la start la private beta June 26, 2008 at 11:39

    [...] facut teste (adica, facem TDD – Test Driven Development), trecem pe cararea mai putin oficiala a modulului cu simpleTest pentru ca cel oficial si inca in beta are un caz pe care nu merge si nu reusim sa gasim cauza in [...]

  • Richard Yumul September 05, 2008 at 06:44

    Is there a way to make cake produce the same resulting HTML when run under the testsuite versus what cake produces when an action is hit thru the web server?

    I’d like to be able to capture the output. In other words, I’d like to be able to be able to capture the output when an action is executed from the testsuite, and that ideally should be the same HTML returned when the action is executed when hit thru the web server. Does that make sense?

    class FooTest extends CakeTestCase {
      function testBar() {
        $this->get('/foo/bar');
        // how do I get the HTML output now?
      }
    }
  • cakebaker September 06, 2008 at 19:14

    @Richard: Well, you can replace the render() method in testable_controller.php with:

    function render($action = null, $layout = null, $file = null) {
        $this->viewContent = parent::render($action, $layout, $file);
    }

    In your test method you can then access the view content with:

    $this->controller->viewContent;
    

    Hope that helps!

  • Doug Kulak April 10, 2009 at 05:55

    Is this still the best way to test CakePHP? I’m brand new to unit testing and want to be sure I’m off on the right track!

  • cakebaker April 10, 2009 at 16:48

    @Doug: Thanks for your comment!

    No, this test suite is outdated, and so I recommend to use the official test suite which is bundled with CakePHP.

    Hope that helps, and happy testing :)

  • Doug Kulak April 10, 2009 at 17:01

    @cakebaker: Thanks for the quick response! I’m relieved to hear that the CakePHP community is on top of things :)

  • Lista.lu – de la start la private beta | Dream Production June 08, 2010 at 14:25

    [...] teste (adica, facem TDD – Test Driven Development), trecem pe cararea mai putin oficiala a modulului cu simpleTest pentru ca cel oficial si inca in beta are un caz pe care nu merge si nu reusim sa gasim cauza in [...]

Bake a comment




(for code please use <code>...</code> [no escaping necessary])

© daniel hofstetter. Licensed under a Creative Commons License