Testing Models in CakePHP - Now let's get rid of the unnecessary ModelTest classes !
Posted on 30/7/08 by Tim Koschützki
Hey folks,
today I committed some stuff that will make a bigger impact on your Unit Testing development in CakePHP. It's a much cleaner way of how you are able to test models. Up until now there was always a need to create a so-called test model that extends your model-under-test in order to overwrite its $useDbConfig setting to be 'test_suite'. By that you ensured that your models run with the test_suite datasource when the tests are run.
Nate proposed ClassRegistry::config(), which allows you to tell the ClassRegistry class which datasource it shall use when
ClassRegistry::init() is used the next time (and thereby a model is instantiated). So what we are doing now in the testsuite is telling the ClassRegistry to use the test_suite db config whenever a new model is instantiated via ClassRegistry::init(). Doesn't make sense to you? Well, let's look at an example.
The following code is directly taken from the CookBook and shows the old way of doing things:
class ArticleTest extends Article {
var $name = 'ArticleTest';
var $useDbConfig = 'test_suite';
}
class ArticleTestCase extends CakeTestCase {
var $fixtures = array('app.article_test');
function testPublished() {
$this->ArticleTest =& new ArticleTest();
$result = $this->ArticleTest->published(array('id', 'title'));
$expected = array(
array('ArticleTest' => array( 'id' => 1, 'title' => 'First Article' )),
array('ArticleTest' => array( 'id' => 2, 'title' => 'Second Article' )),
array('ArticleTest' => array( 'id' => 3, 'title' => 'Third Article' ))
);
$this->assertEqual($result, $expected);
}
}
So far so good. The ArticleTest model extends the imported Article model to overwrite the datasource setting. Then it loads the ArticleTest fixture to use its data throughout the test. The drawback: A whole class that does practically nothing. In a bigger system with plenty of models and plenty of tests this can contribute to your hair loss - especially when you forget to name your fixture properly or provide the $name property in the Test Model.
With the new code in place, this will just work as well:
class ArticleTestCase extends CakeTestCase {
var $fixtures = array('app.article');
function testPublished() {
$this->Article =& ClassRegistry::init('Article');
$result = $this->Article->published(array('id', 'title'));
$expected = array(
array('Article' => array( 'id' => 1, 'title' => 'First Article' )),
array('Article' => array( 'id' => 2, 'title' => 'Second Article' )),
array('Article' => array( 'id' => 3, 'title' => 'Third Article' ))
);
$this->assertEqual($result, $expected);
}
}
So we got rid of the ArticleTest class and can now use the normal ArticleFixture that we probably use throughout the system anyway to provide some standard data for the application. Via the $test_suite setting in your database.php file you can control where and how the test data is imported.
The only change to your code: Instantiate the subject under test via ClassRegistry::init() instead of instantiating directly via the new operator.
To me this seems like the proper way to do it. No need for further unnecessary classes. Everything clean and controllable. Do you agree?
-- Tim Koschuetzki aka DarkAngelBGE
PS: Kudos again to Nate for ClassRegistry::config() !