views:

258

answers:

2

So I'm having trouble with unit testing CakePHP models. Simple stuff like writing tests that my validation rules are catching errors etc.

To begin with, I have a model called NewsItem. Its defined in my MySQL database using the following schema

CREATE TABLE news_items (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(140) NOT NULL,
    body TEXT NOT NULL,
    modified DATETIME DEFAULT NULL,
    created DATETIME DEFAULT NULL
);

The model is following

<?php
class NewsItem extends AppModel {

    var $name = 'NewsItem';
    var $validate = array(
     'title' => array(
      'titleRule-1' => array(
       'rule' => array('maxLength', 140),
       'message' => 'News item\'s title\'s length exceeds limit of 140 characters'
      ),
      'titleRule-2' => array(
       'rule' => 'alphaNumeric',
       'required' => true,
       'message' => 'Cannot save news item without a title'
      )
     )
    );

}
?>

And in my test case I have

// Validation lets All data good through
function testValidationAllowsNormalData()
{
 $this->assertTrue($this->NewsItem->save(array('NewsItem' => array('title' => 'A news item', 'body' => 'Some news'))));
}

However I'm my test case fails. Any ideas, suggestions, comments?

A: 

I'm not hugely familiar with cakePHP, but wouldn't save attempt to persist to the database? That should not be in a unit test if that is the case...

Myles
Cake is clever enough to use a separate database for tests.
deceze
That's great and all, but that still shouldn't be part of a unit test. A unit test, tests well the unit. If you throw a db connection into the mix you're adding more variables and not isolating the unit.
Myles
So you're saying one should test every unit, just not the unit that saves data? What if that's exactly what you want to test? Sure it adds one more variable into the mix, but so what? If it fails you need to debug the test as well, if it works you know that both the unit and the database are okay.
deceze
From our very own SO http://stackoverflow.com/questions/61400/what-makes-a-good-unit-test . "Tests should test only one thing at a time. Multiple assertions are okay as long as they are all testing one feature/behavior. When a test fails, it should pinpoint the location of the problem."There's some good reads in that post, you should check it out.
Myles
I agree to a point. Still, what if you'd want to test the whole chain of `beforeValidate()`, validation, `beforeSave()`, `save()`, `afterSave()` and whatever else is involved in the process? Every piece may work by itself, but you may have screwed up the logic of how they play together. In other words: What's *wrong* with testing `Model::save()`, as long as you're still testing all the other parts separately? (Which I'm sure the OP will do, right, RIGHT?)
deceze
At the risk of repeating myself...If you need to test the sequence of those calls, then there should be a test that makes those calls in sequence. The data you're working on/with should be a fixed known quantity, that's why they call them fixtures. By going out to the database you are no longer able to pinpoint a problem based on a test failure, making the unit test significantly less useful.
Myles
I'd call `array('NewsItem' => array('title' => 'A news item', 'body' => 'Some news'))` a rather fixed, known quantity. Are you assuming that the test database is not cleared before and after a test? What if you change the sequence of calls made during a `save()` in your code, but forget to change it in the test, and are emulating a different sequence than will actually happen during a real `save()`? In other words: What *wrong* with testing `save()`?
deceze
How do you test save() if not using the database connection? That's what i want to know. Because i seem to have been doing this the wrong way for ages if you can't test a method because it uses a database connection.
Peter Lindqvist
Maybe this is a shortcoming of CakePHP's model, but if I were writing this class, I would have either a constructor param or a property that is of type IConnection that is responsible for persistence. In the unit test I would create some sort of mock object that implements IConnection, pass that into the model and check my save results against that, thereby removing the need for a database connection.
Myles
You could use a "blackhole" datasource for tests if you wanted to. I still don't understand why you're so vehemently against actually writing to a database. If that's the only test that's being administered it's indeed not really granular enough. But if it's one test among others that may uncover a critical problem in the database/schema/connection layer, how can it hurt?
deceze
+1  A: 

The alphaNumeric validation rule only allows, well, alphanumeric characters, i.e. no spaces. So your test fails correctly.

deceze