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

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/
@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!
Bake2 will not work with non standard webroot and app paths :(
Is there any chance to do that ?
@Martin: What do you try to accomplish? And what is the problem?
Personally I use it without problems with an advanced setup of CakePHP.
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 :)
@Martin: Please be more specific. What command are you using? What errors do you get? Provide as much information as possible.
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
@Martin: No, there is no return code.
[...] I still prefer my own test suite, even though it requires a bit more configuration [...]
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?
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?
OK, I solved it. My app didn’t have an entry in app.ini, so the test task couldn’t resolve aliases and paths.
@hydra12: Good to hear you could solve the problem in the meantime :)
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
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;
……
the order of deletes shoudl be the reverse of the inserts in the __loadfixtures function…
@krishnan: Thanks for your comments, I will fix this issue.
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
@krishnan: Hm, why do you have to set such a path? And what do you mean with point b), I don’t understand it…
[...] 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. [...]
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?
@Lars: It contains an alias as key plus a path to the app folder of your application, for example:
HTH
How use command line
@one_cart: With the current version you can use it like:
where “your-app-alias” is the key in apps.ini.
HTH
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
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 ? ;)
@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 ;-)
Is this still current? Is this still the best way to test cakephp apps?
@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.
[...] О тестах в CakePHP + инструкция по тестированию [...]
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.
@Bruno: What do you think could be better? Where did you have problems?
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,
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 :)
@abstract: Thanks for the fixes! I applied them and uploaded a new version.
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! :)
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!
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!
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!
@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:
I have to update the documentation as it is a bit outdated. Hope that helps in the meantime.
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
@Rich: Thanks for the link!
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?
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?
@Bhushan: Hm, did you add an application alias with the path to the app folder to apps.ini? Something like:
Hope that helps.
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.
@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…
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!
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.
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 :)
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
@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]“?
[...] 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 [...]
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?
@Richard: Well, you can replace the render() method in testable_controller.php with:
In your test method you can then access the view content with:
Hope that helps!
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!
@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 :)
@cakebaker: Thanks for the quick response! I’m relieved to hear that the CakePHP community is on top of things :)
[...] 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 [...]