This document is for a version of CakePHP that is no longer supported. Please upgrade to a newer release!

Testen

Mit CakePHP 1.2_RC2 wurde eine umfangreiche Test-Umgebung integriert. Diese Test-Umgebung ist eine Ergänzung bzw. Erweiterung zur bisherigen SimpleTest-Umgebung für PHP. Dieser Abschnitt befasst sich damit, wie man Tests vorbereitet, aufbaut und ausführt.

Vorbereitung zum Test-Marathon

Fertig zum Beginn der Tests? Optimal! Dann lass uns endlich anfangen! :-)

Installation von SimpleTest

Das in CakePHP 1.2 enthaltene Test-Framework ist aufgebaut auf dem SimpleTest-Test-Framework. SimpleTest ist in der Standard-CakePHP-Installation nicht enthalten, somit müssen wir das nun runterladen. Hier findest du SimpleTest: https://simpletest.sourceforge.net/. Besorg dir die letzte Version und entpack die Dateien entweder in deinen /cake/vendors/ oder in den /app/vendors/ Ordner, je nach euren Anforderungen. Du solltest also jetzt einen ../vendors/simpletest/ Ordner samt der dazugehörigen SimpleTest-Dateien haben!? Vergiss nicht, dass du in deiner app/config/core.php das „DEBUG-level“ mindestens auf 1 setzt, bevor du mit irgendwelchen Test`s anfängst!

Starten der integrierten Test-Funktionen

CakePHP 1.2 kommt mit einem Bündel an Test-Fallbeispielen, involviert in die Core-CakePHP-Funktionen, daher. Auf diese Test`s kann zugegriffen werden indem man mit dem Browser https://deine.cake.adresse/dein_cake_ordner/test.php (je nachdem wie euer spezifisches Setup aussieht..), ansteuert. Probier eine von den Core-Test-Gruppen aus (indem du die Links anklickst… ;-D). Das Ausführen der Test-Gruppen könnte möglicherweise ein Weilchen dauern, aber schlussendlich sollte etwas wie: „2/2 test casese complete: 49 passes, 0 fails and 0 exceptions.“ angezeigt werden. Gratuliere, du bist jetzt bereit um Schreib-Test`s zu machen!

Test Überblick - Einzel-Test vs. Web-Test

Das CakePHP-Test-Framework unterstützt 2 Arten von Tests. Die eine ist der Unit-Test bzw. Einzel-Test, mit welchem du kleine Teile deines Codes testen kannst, wie eine „Methode in einer Aktion“ oder eine „Aktion in einem Controller“. Die andere unterstützte Test-Art ist der Web-Test, mit dem du die Arbeit der Tests, durch Seiten-Navigation, Formulare ausfüllen, Links anklicken u.s.w, automatisieren kannst.

Vorbereiten der Test Daten

Über „fixtures“

Wenn man Code testet der auf models und Daten basiert, dann kann man für eines davon *fixtures* nutzen, als einen Weg um temporäre Datensätze mit Beispieldaten zu laden und diese dann zum testen zu verwenden.

Der Vorteil beim verwenden von fixtures liegt darin, dass dein Test keine Chance hat geladene Anwendungsdaten zu zerstören. Zusätzlich kannst du damit Anfangen deine Code-Priorität, mit aktuell gefertigten Live-Inhalten für eine Anwendung, zu testen.

CakePHP versucht die Verbindung namens $test in deiner /app/config/database.php Einstellungs-Datei zu benutzen. Wenn diese Verbindung nicht brauchbar ist, dann wird die $default Datenbank-Verbindung genutzt und die Test-Tabellen werden in der dort definierten Datenbank erstellt.

In einem anderen Fall, wird „test_suite“ zu deinem eigenen Tabellen-Zusatz(falls vorhanden) hinzu gefügt um einer Kollision, mit einer vorhandenen Tabelle, vorzubeugen.

CakePHP bietet folgende Möglichkeiten wärend der Tour durch fixtures-basierte Test Fälle:

  1. Erstellt Tabellen für alles was von den fixtures gebraucht wird

  2. Füllt Tabellen mit Daten, wenn Daten in fixture angefordert werden

  3. Startet Test Methoden

  4. Entleert fixture-Tabellen

  5. Löscht fixture-Tabellen aus der Datenbank

Erstellen von Vorrichtungen (fixtures)

Beim Erstellen von Vorrichtungen solltest du hauptsächlich 2 Dinge definieren: Wie wurde die Tabelle erstellt (welche Felder sind ein Teil der Tabelle) und welche Datensätze werden zu Beginn die Test-Tabelle belegen. Dann lass uns mal die erste Vorrichtung (fixture) erstellen, womit wir dann unsere Modell-Artikel testen. Erstelle eine Datei mit dem Namen article_test_fixture.php in deinem ../app/tests/fixtures/ Ordner, mit folgendem Inhalt:

<?php
 class ArticleTestFixture extends CakeTestFixture {
      var $name = 'ArticleTest';

      var $fields = array(
          'id' => array('type' => 'integer', 'key' => 'primary'),
          'title' => array('type' => 'string', 'length' => 255, 'null' => false),
          'body' => 'text',
          'published' => array('type' => 'integer', 'default' => '0', 'null' => false),
          'created' => 'datetime',
          'updated' => 'datetime'
      );
      var $records = array(
          array ('id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', 'published' => '1', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'),
          array ('id' => 2, 'title' => 'Second Article', 'body' => 'Second Article Body', 'published' => '1', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31'),
          array ('id' => 3, 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => '1', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31')
      );
 }
 ?>

Wir brauchen $fields um zu spezifizieren welche Felder Bestandteil der Tabelle sind und wie diese Felder definiert sind. Das Format das gebraucht wird, um diese Felder zu definieren, ist dass gleiche das in der Funktion generateColumnSchema(), aus der Cake-Datenbank-Engineklasse, definiert wird (zum Beispiel die Datei dbo_mysql.php). Lass uns mal schauen welche Attribute ein Feld haben kann und was diese aussagen:

type

CakePHP`s interner Daten-Typ. Momentan unterstützt: string (Speicherort für diverse Zeichenketten/-folgen), text (Speicherort für Texte), integer (Speicherort für Ganzzahlen), float (Speicherort für Fliesskommazahlen), datetime (Speicherort für Datum+Zeit), timestamp (Speicherort für Zeitmarken), time (Speicherort für Zeiten), date (Speicherort für Datumsangaben), und binary (Speicherort für Binärzahlen/zeichen)

key

auf primary setzen damit das Feld AUTO-ANWACHSEN lassen zu können und um einen Hauptschlüssel für die Tabelle zu erstellen.

length

setzen um dem Feld die spezielle länge zu geben die es haben sollte.

null

Setze den Wert auf „true“ („wahr“ => Zum Erlauben von NULLen) oder „false“ („unwahr“ => um NULLen zu verbieten)

default

Standart-Einheit des Feldes..

Zum Schluss könnten wir eine Reihe von Datensätzen setzen, welche publiziert werden nach dem die Test-Tabelle erstellt wurde. Das Format ist ziemlich ordentlich und braucht wenig weitere Erklärung. Versuch daran zu denken, dass jeder Datensatz im $records Ordner einen Schlüssel haben muss für jedes Feld das im $fields Ordner angegeben ist. Falls ein Feld für für einen speziellen Datensatz einen NULL-Wert braucht, dann deklariere den Wert des Schlüssel`s als NULL.

Importieren von Datensätzen und Tabellen-Daten

Deine Anwendung mag möglicherweise funktionierende Entwürfe beinhalten, mit echten untereinander verknüpften Daten, und du könntest dich dazu entscheiden deine Entwürfe mit diesen Daten zu testen… Das wäre wohl dann eine Doppelanstrengung, wenn man die Tabellendefinition festlegen und/oder die Datensätze mitsamt deinen Inhalten definieren sollte. Glücklicherweise, gibt es da`die Möglichkeit zum festlegen der Tabellendefinition und/oder den Datensätzen für bestimmte Inhalte, die kommen von fertigen Modellen oder einer bereits vorhandenen Tabelle. Dann lass uns mal eine Musteraufgabe anfangen. Mal angenommen du hast ein Model mit dem Namen Article in deiner Anwendung verfügbar (das weist auf die Tabelle articles hin!), ändere die Muster-Inhalte aus der vorherigen Sektion (app/tests/fixtures/article_test_fixture.php) wie folgt:

<?php
  class ArticleTestFixture extends CakeTestFixture {
         var $name = 'ArticleTest';
         var $import = 'Article';
  }
  ?>

Die og Anweisungen weisen die Testfolge an, deine Tabellendefinition aus der Tabelle, welche im Model Article verlinkt ist, zu importieren. Du kannst dazu jegliches Muster verwenden, dass du in deiner Anwendung verfügbar ist. Oben die Anweisung importiert keinerlei Datensätze, dies kannst du dann tun wenn du die Anweisung umänderst zu:

<?php
class ArticleTestFixture extends CakeTestFixture {
    var $name = 'ArticleTest';
    var $import = array('model' => 'Article', 'records' => true);
}
?>

Wenn du andererseits eine Tabelle erstellt hast, aber keine Muster-Inhalte dazu vorhanden sind, dann kannst du anweisen dass ein import veranlasst wird, indem anstatt der Model-Daten dessen Tabellen-Informationen ausgelesen werden. Zum Bsp:

<?php
  class ArticleTestFixture extends CakeTestFixture {
         var $name = 'ArticleTest';
         var $import = array('table' => 'articles');
  }
?>

Der obere Code-Fetzen wird einen import der Tabellendefinition von der Tabelle articles veranlassen, indem die CakePHP-Datenbankverbindung ‚default‘ genutzt wird. Wenn du die aktuell verwendete Datenbank-Verbindung ändern möchtest, musst du folgende Zeilen ändern:

<?php
  class ArticleTestFixture extends CakeTestFixture {
  var $name = 'ArticleTest';
  var $import = array('table' => 'articles', 'connection' => 'other');
  }
  ?>

Seit es deine CakePHP Datenbankverbindung verwendet hat wird es, wenn da irgendwelche Tabellen-Vorzeichen erkennbar sind, automatisch gebraucht, da attraktive Tabellen-Informationen automatisch verwendet werden. Die zwei Code-Schnipsel da oben importieren keine Datensätze aus der Tabelle. Um also die Inhalte zum import der Datensätze zu zwingen, ädere folgende Dinge:

<?php
  class ArticleTestFixture extends CakeTestFixture {
         var $name = 'ArticleTest';
         var $import = array('table' => 'articles', 'records' => true);
  }
?>

Du kannst natürlich auch deine Tabellendefinition aus einem exitierenden Muster oder einer Tabelle importieren, aber halte deine Datensätze direkt definiert, genau nach den Vorgaben aus der vorhergehenden Sektion. zum Beispiel:

<?php
  class ArticleTestFixture extends CakeTestFixture {
         var $name = 'ArticleTest';
         var $import = 'Article';

         var $records = array(
             array ('id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', 'published' => '1', 'created' => '2007-03-18 10:39:23', 'updated' => '2007-03-18 10:41:31'),
             array ('id' => 2, 'title' => 'Second Article', 'body' => 'Second Article Body', 'published' => '1', 'created' => '2007-03-18 10:41:23', 'updated' => '2007-03-18 10:43:31'),
             array ('id' => 3, 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => '1', 'created' => '2007-03-18 10:43:23', 'updated' => '2007-03-18 10:45:31')
         );
  }
?>

Tests erstellen

Zunächst erstmal eine Latte von Regeln oder Richtlinien bezüglich der Test`s:

  1. PHP Dateien die Test`s beinhalten, sollten im app/tests/cases/[some_folder] Ordner sein.

  2. Die Dateinamen dieser Dateien sollten am Ende etwa so aussehen:.test.php anstatt sowas: .php.

  3. Die Klassen die die Test enthalten, müssen CakeTestCase oder CakeWebTestCase ausführen (extend).

  4. Der Name von einigen Methoden die ebenfalls Tests enthalten können (d.h. enthält eine Erklärung) sollten dann mit test beginnen, wie zum Beispiel: testPublished().

Wenn du einen Test-Fall erstellt hast, dann kannst du diesen starten, indem du mit deinem Browser folgende Adresse ansteuerst: https://deine.cake.domain/cake_ordner/test.php (abhängig von deinem persönlichen Setup für CakePHP!). Im Anschluss an`s durchklicken der Programm-Test-Möglichkeiten, bitte den Link zu deiner persönlichen Datei anklicken.

CakeTestCase Callback Methods

If you want to sneak in some logic just before or after an individual CakeTestCase method, and/or before or after your entire CakeTestCase, the following callbacks are available:

start()

First method called in a test case.

end()

Last method called in a test case.

startCase()

called before a test case is started.

endCase()

called after a test case has run.

before($method)

Announces the start of a test method.

after($method)

Announces the end of a test method.

startTest($method)

Called just before a test method is executed.

endTest($method)

Called just after a test method has completed.

Testing models

Erstellen einer Versuchs-Anwendung (test case)

Lass uns darauf einigen dass wir unseren Muster-Artikel (article model) bereits unter ../app/models/article.php erstellt haben und dieser sollte in etwa wie folgt aussehen:

<?php
  class Article extends AppModel {
         var $name = 'Article';

         function published($fields = null) {
             $conditions = array(
                 $this->name . '.published' => 1
             );

             return $this->findAll($conditions, $fields);
         }

  }
?>

Wir möchten jetzt einen Versuch aufbauen welcher Beispielmodule verwenden wird, allerdings durch Vorrichtungen für Versuche, lässt sich einiges an Funktionalität im Versuch testen. Die CakePHP Versuchs-Umgebung läd nur einen sehr kleinen Teil der Anwendungen (um Versuche isoliert zu lassen), somit müssen wir zum starten das vorhergehende Module verwenden (in diesem Fall ist das Anwendungsmodul ja schon fertig definiert), dann informiere die Versuchs-Umgebung darüber, dass wir das Modul testen wollen, indem wir herausfinden welche Datenbank-Konfiguration benutzt werden sollte!. Die CakePHP Test-Umgebung ermöglicht eine Datenbank-Konfiguration namen`s test_suite, diese wird gebraucht für alle Module, die auf Vorrichtungen angewiesen sind. Der Datensatz $useDbConfig zu dieser Konfigurationsdatei lässt CakePHP wissen das dieses Modul die test_suite DB-Verbindung benutzt. Seit wir darüber nachdenken all unsere Anwendungen nochmal wiederzuverwerten umd das ganze dazu benutzen das wir alle want to reuse all our existing modules we will create a test model that will extend from Article, set $useDbConfig and $name appropiately. Let’s now create a file named article.test.php in your app/tests/cases/models directory, with the following contents:

<?php
  App::import('Model','Article');

  class ArticleTest extends Article {
         var $name = 'ArticleTest';
         var $useDbConfig = 'test_suite';
  }

  class ArticleTestCase extends CakeTestCase {
         var $fixtures = array( 'app.article_test' );
  }
?>

Wie du erkennen solltest we’re not really adding any test methods yet, we have just defined our ArticleTest model (that inherits from Article), and created the ArticleTestCase. In variable $fixtures we define the set of fixtures that we’ll use.

Creating a test method

Let’s now add a method to test the function published() in the Article model. Edit the file app/tests/cases/models/article.test.php so it now looks like this:

<?php
  App::import('Model', 'Article');

  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);
      }
  }
  ?>

You can see we have added a method called testPublished(). We start by creating an instance of our fixture based Article model, and then run our published() method. In $expected we set what we expect should be the proper result (that we know since we have defined which records are initally populated to the article table.) We test that the result equals our expectation by using the assertEqual method. See the section Creating Tests for information on how to run the test.

Testing controllers

Creating a test case

Say you have a typical articles controller, with its corresponding model, and it looks like this:

<?php
class ArticlesController extends AppController {
   var $name = 'Articles';
   var $helpers = array('Ajax', 'Form', 'Html');

   function index($short = null) {
     if (!empty($this->data)) {
       $this->Article->save($this->data);
     }
     if (!empty($short)) {
       $result = $this->Article->findAll(null, array('id',
          'title'));
     } else {
       $result = $this->Article->findAll();
     }

     if (isset($this->params['requested'])) {
       return $result;
     }

     $this->set('title', 'Articles');
     $this->set('articles', $result);
   }
}
?>

Create a file named articles_controller.test.php in your app/tests/cases/controllers directory and put the following inside:

<?php
class ArticlesControllerTest extends CakeTestCase {
   function startCase() {
     echo '<h1>Starting Test Case</h1>';
   }
   function endCase() {
     echo '<h1>Ending Test Case</h1>';
   }
   function startTest($method) {
     echo '<h3>Starting method ' . $method . '</h3>';
   }
   function endTest($method) {
     echo '<hr />';
   }
   function testIndex() {
     $result = $this->testAction('/articles/index');
     debug($result);
   }
   function testIndexShort() {
     $result = $this->testAction('/articles/index/short');
     debug($result);
   }
   function testIndexShortGetRenderedHtml() {
     $result = $this->testAction('/articles/index/short',
     array('return' => 'render'));
     debug(htmlentities($result));
   }
   function testIndexShortGetViewVars() {
     $result = $this->testAction('/articles/index/short',
     array('return' => 'vars'));
     debug($result);
   }
   function testIndexFixturized() {
     $result = $this->testAction('/articles/index/short',
     array('fixturize' => true));
     debug($result);
   }
   function testIndexPostFixturized() {
     $data = array('Article' => array('user_id' => 1, 'published'
          => 1, 'slug'=>'new-article', 'title' => 'New Article', 'body' => 'New Body'));
     $result = $this->testAction('/articles/index',
     array('fixturize' => true, 'data' => $data, 'method' => 'post'));
     debug($result);
   }
}
?>

The testAction method

The new thing here is the testAction method. The first argument of that method is the Cake url of the controller action to be tested, as in ‚/articles/index/short‘.

The second argument is an array of parameters, consisting of:

return
Set to what you want returned.

Valid values are:

  • ‚vars‘ - You get the view vars available after executing action

  • ‚view‘ - You get The rendered view, without the layout

  • ‚contents‘ - You get the rendered view’s complete html, including the layout

  • ‚result‘ - You get the returned value when action uses $this->params[‚requested‘].

The default is ‚result‘.

fixturize

Set to true if you want your models auto-fixturized (so your application tables get copied, along with their records, to test tables so if you change data it does not affect your real application.) If you set ‚fixturize‘ to an array of models, then only those models will be auto-fixturized while the other will remain with live tables. If you wish to use your fixture files with testAction() do not use fixturize, and instead just use fixtures as you normally would.

method

set to ‚post‘ or ‚get‘ if you want to pass data to the controller

data

the data to be passed. Set it to be an associative array consisting of fields => value. Take a look at function testIndexPostFixturized() in above test case to see how we emulate posting form data for a new article submission.

Pitfalls

If you use testAction to test a method in a controller that does a redirect, your test will terminate immediately, not yielding any results. See https://trac.cakephp.org/ticket/4154 for a possible fix.

For an in-depth explanation of controller testing please see this blog post by Mark Story Testing CakePHP Controllers the hard way.

Testing Helpers

Since a decent amount of logic resides in Helper classes, it’s important to make sure those classes are covered by test cases.

Helper testing is a bit similar to the same approach for Components. Suppose we have a helper called CurrencyRendererHelper located in app/views/helpers/currency_renderer.php with its accompanying test case file located in app/tests/cases/helpers/currency_renderer.test.php

Creating Helper test, part I

First of all we will define the responsibilities of our CurrencyRendererHelper. Basically, it will have two methods just for demonstration purpose:

function usd($amount)

This function will receive the amount to render. It will take 2 decimal digits filling empty space with zeros and prefix ‚USD‘.

function euro($amount)

This function will do the same as usd() but prefix the output with ‚EUR‘. Just to make it a bit more complex, we will also wrap the result in span tags:

<span class="euro"></span>

Let’s create the tests first:

<?php

//Import the helper to be tested.
//If the tested helper were using some other helper, like Html,
//it should be impoorted in this line, and instantialized in startTest().
App::import('Helper', 'CurrencyRenderer');

class CurrencyRendererTest extends CakeTestCase {
    private $currencyRenderer = null;

    //Here we instantiate our helper, and all other helpers we need.
    public function startTest() {
        $this->currencyRenderer = new CurrencyRendererHelper();
    }

    //testing usd() function.
    public function testUsd() {
        $this->assertEqual('USD 5.30', $this->currencyRenderer->usd(5.30));
        //We should always have 2 decimal digits.
        $this->assertEqual('USD 1.00', $this->currencyRenderer->usd(1));
        $this->assertEqual('USD 2.05', $this->currencyRenderer->usd(2.05));
        //Testing the thousands separator
        $this->assertEqual('USD 12,000.70', $this->currencyRenderer->usd(12000.70));
    }
}

Here, we call usd() with different parameters and tell the test suite to check if the returned values are equal to what is expected.

Executing the test now will result in errors (because currencyRendererHelper doesn’t even exist yet) showing that we have 3 fails.

Once we know what our method should do, we can write the method itself:

<?php
class CurrencyRendererHelper extends AppHelper {
    public function usd($amount) {
        return 'USD ' . number_format($amount, 2, '.', ',');
    }
}

Here we set the decimal places to 2, decimal separator to dot, thousands separator to comma, and prefix the formatted number with ‚USD‘ string.

Save this in app/views/helpers/currency_renderer.php and execute the test. You should see a green bar and messaging indicating 4 passes.

Testing components

Lets assume that we want to test a component called TransporterComponent, which uses a model called Transporter to provide functionality for other controllers. We will use four files:

  • A component called Transporters found in app/controllers/components/transporter.php

  • A model called Transporter found in app/models/transporter.php

  • A fixture called TransporterTestFixture found in app/tests/fixtures/transporter_fixture.php

  • The testing code found in app/tests/cases/transporter.test.php

Initializing the component

Since CakePHP discourages from importing models directly into components we need a controller to access the data in the model.

If the startup() function of the component looks like this:

public function startup(&$controller){
          $this->Transporter = $controller->Transporter;
 }

then we can just design a really simple fake class:

class FakeTransporterController {}

and assign values into it like this:

$this->TransporterComponentTest = new TransporterComponent();
$controller = new FakeTransporterController();
$controller->Transporter = new TransporterTest();
$this->TransporterComponentTest->startup(&$controller);

Creating a test method

Just create a class that extends CakeTestCase and start writing tests!

class TransporterTestCase extends CakeTestCase {
    var $fixtures = array('transporter');
    function testGetTransporter() {
          $this->TransporterComponentTest = new TransporterComponent();
          $controller = new FakeTransporterController();
          $controller->Transporter = new TransporterTest();
          $this->TransporterComponentTest->startup(&$controller);

          $result = $this->TransporterComponentTest->getTransporter("12345", "Sweden", "54321", "Sweden");
          $this->assertEqual($result, 1, "SP is best for 1xxxx-5xxxx");

          $result = $this->TransporterComponentTest->getTransporter("41234", "Sweden", "44321", "Sweden");
          $this->assertEqual($result, 2, "WSTS is best for 41xxx-44xxx");

          $result = $this->TransporterComponentTest->getTransporter("41001", "Sweden", "41870", "Sweden");
          $this->assertEqual($result, 3, "GL is best for 410xx-419xx");

          $result = $this->TransporterComponentTest->getTransporter("12345", "Sweden", "54321", "Norway");
          $this->assertEqual($result, 0, "Noone can service Norway");
   }
}

Web testing - Testing views

Most, if not all, CakePHP projects result in a web application. While unit tests are an excellent way to test small parts of functionality, you might also want to test the functionality on a large scale. The CakeWebTestCase class provides a good way of doing this testing from a user point-of-view.

About CakeWebTestCase

CakeWebTestCase is a direct extension of the SimpleTest WebTestCase, without any extra functionality. All the functionality found in the SimpleTest documentation for Web testing is also available here. This also means that no functionality other than that of SimpleTest is available. This means that you cannot use fixtures, and all web test cases involving updating/saving to the database will permanently change your database values. Test results are often based on what values the database holds, so making sure the database contains the values you expect is part of the testing procedure.

Creating a test

In keeping with the other testing conventions, you should create your view tests in tests/cases/views. You can, of course, put those tests anywhere but following the conventions whenever possible is always a good idea. So let’s create the file tests/cases/views/complete_web.test.php

First, when you want to write web tests, you must remember to extend CakeWebTestCase instead of CakeTestCase:

class CompleteWebTestCase extends CakeWebTestCase

If you need to do some preparation before you start the test, create a constructor:

function CompleteWebTestCase(){
  //Do stuff here
}

When writing the actual test cases, the first thing you need to do is get some output to look at. This can be done by doing a get or post request, using get()or post() respectively. Both these methods take a full url as the first parameter. This can be dynamically fetched if we assume that the test script is located under http://your.domain/cake/folder/webroot/test.php by typing:

$this->baseurl = current(split("webroot", $_SERVER['PHP_SELF']));

You can then do gets and posts using Cake urls, like this:

$this->get($this->baseurl."/products/index/");
$this->post($this->baseurl."/customers/login", $data);

The second parameter to the post method, $data, is an associative array containing the post data in Cake format:

$data = array(
  "data[Customer][mail]" => "[email protected]",
  "data[Customer][password]" => "userpass");

When you have requested the page you can do all sorts of asserts on it, using standard SimpleTest web test methods.

Walking through a page

CakeWebTest also gives you an option to navigate through your page by clicking links or images, filling forms and clicking buttons. Please refer to the SimpleTest documentation for more information on that.

Testing plugins

Tests for plugins are created in their own directory inside the plugins folder.

/app
     /plugins
         /pizza
             /tests
                  /cases
                  /fixtures
                  /groups

They work just like normal tests but you have to remember to use the naming conventions for plugins when importing classes. This is an example of a testcase for the PizzaOrder model from the plugins chapter of this manual. A difference from other tests is in the first line where ‚Pizza.PizzaOrder‘ is imported. You also need to prefix your plugin fixtures with ‚plugin.plugin_name.‚.

<?php
App::import('Model', 'Pizza.PizzaOrder');

class PizzaOrderCase extends CakeTestCase {

    // Plugin fixtures located in /app/plugins/pizza/tests/fixtures/
    var $fixtures = array('plugin.pizza.pizza_order');
    var $PizzaOrderTest;

    function testSomething() {
        // ClassRegistry makes the model use the test database connection
        $this->PizzaOrderTest =& ClassRegistry::init('PizzaOrder');

        // do some useful test here
        $this->assertTrue(is_object($this->PizzaOrderTest));
    }
}
?>

If you want to use plugin fixtures in the app tests you can reference them using ‚plugin.pluginName.fixtureName‘ syntax in the $fixtures array.

That is all there is to it.

Miscellaneous

Customizing the test reporter

The standard test reporter is very minimalistic. If you want more shiny output to impress someone, fear not, it is actually very easy to extend. The only danger is that you have to fiddle with core Cake code, specifically /cake/tests/libs/cake_reporter.php.

To change the test output you can override the following methods:

paintHeader()

Prints before the test is started.

paintPass()

Prints everytime a test case has passed. Use $this->getTestList() to get an array of information pertaining to the test, and $message to get the test result. Remember to call parent::paintPass($message).

paintFail()

Prints everytime a test case has failed. Remember to call parent::paintFail($message).

paintFooter()

Prints when the test is over, i.e. when all test cases has been executed.

If, when running paintPass and paintFail, you want to hide the parent output, enclose the call in html comment tags, as in:

echo "\n<!-- ";
parent::paintFail($message);
echo " -->\n";

A sample cake_reporter.phpsetup that creates a table to hold the test results follows:

<?php
 /**
 * CakePHP(tm) Tests <https://trac.cakephp.org/wiki/Developement/TestSuite>
 * Copyright 2005-2008, Cake Software Foundation, Inc.
 *                              1785 E. Sahara Avenue, Suite 490-204
 *                              Las Vegas, Nevada 89104
 *
 *  Licensed under The Open Group Test Suite License
 *  Redistributions of files must retain the above copyright notice.
 */
 class CakeHtmlReporter extends HtmlReporter {
 function CakeHtmlReporter($characterSet = 'UTF-8') {
 parent::HtmlReporter($characterSet);
 }

function paintHeader($testName) {
  $this->sendNoCacheHeaders();
  $baseUrl = BASE;
  print "<h2>$testName</h2>\n";
  print "<table style=\"\"><th>Res.</th><th>Test case</th><th>Message</th>\n";
  flush();
 }

 function paintFooter($testName) {
   $colour = ($this->getFailCount() + $this->getExceptionCount() > 0 ? "red" : "green");
   print "</table>\n";
   print "<div style=\"";
   print "padding: 8px; margin-top: 1em; background-color: $colour; color: white;";
   print "\">";
   print $this->getTestCaseProgress() . "/" . $this->getTestCaseCount();
   print " test cases complete:\n";
   print "<strong>" . $this->getPassCount() . "</strong> passes, ";
   print "<strong>" . $this->getFailCount() . "</strong> fails and ";
   print "<strong>" . $this->getExceptionCount() . "</strong> exceptions.";
   print "</div>\n";
 }

 function paintPass($message) {
   parent::paintPass($message);
   echo "<tr>\n\t<td width=\"20\" style=\"border: dotted 1px; border-top: hidden; border-left: hidden;                  border-right: hidden\">\n";
   print "\t\t<span style=\"color: green;\">Pass</span>: \n";
   echo "\t</td>\n\t<td width=\"40%\" style=\"border: dotted 1px; border-top: hidden; border-left: hidden; border-right: hidden\">\n";
   $breadcrumb = $this->getTestList();
   array_shift($breadcrumb);
   array_shift($breadcrumb);
   print implode("-&gt;", $breadcrumb);
   echo "\n\t</td>\n\t<td width=\"40%\" style=\"border: dotted 1px; border-top: hidden; border-left: hidden; border-right: hidden\">\n";
   $message = split('at \[', $message);
   print "-&gt;$message[0]<br />\n\n";
   echo "\n\t</td>\n</tr>\n\n";
 }

 function paintFail($message) {
   echo "\n<!-- ";
   parent::paintFail($message);
   echo " -->\n";
   echo "<tr>\n\t<td width=\"20\" style=\"border: dotted 1px; border-top: hidden; border-left: hidden; border-right: hidden\">\n";
   print "\t\t<span style=\"color: red;\">Fail</span>: \n";
   echo "\n\t</td>\n\t<td width=\"40%\" style=\"border: dotted 1px; border-top: hidden; border-left: hidden; border-right: hidden\">\n";
   $breadcrumb = $this->getTestList();
   print implode("-&gt;", $breadcrumb);
   echo "\n\t</td>\n\t<td width=\"40%\" style=\"border: dotted 1px; border-top: hidden; border-left: hidden; border-right: hidden\">\n";
   print "$message";
   echo "\n\t</td>\n</tr>\n\n";
 }

 function _getCss() {
   return parent::_getCss() . ' .pass { color: green; }';
 }

 }
 ?>

Grouping tests

If you want several of your test to run at the same time, you can try creating a test group. Create a file in /app/tests/groups/ and name it something like your_test_group_name.group.php. In this file, extend GroupTest and import test as follows:

<?php
class TryGroupTest extends GroupTest {
  var $label = 'try';
  function tryGroupTest() {
    TestManager::addTestCasesFromDirectory($this, APP_TEST_CASES . DS . 'models');
  }
}
?>

The code above will group all test cases found in the /app/tests/cases/models/ folder. To add an individual file, use TestManager::addTestFile($this, filename).

Running tests in the Command Line

If you have simpletest installed you can run your tests from the command line of your application.

from app/

cake testsuite help
Usage:
    cake testsuite category test_type file
        - category - "app", "core" or name of a plugin
        - test_type - "case", "group" or "all"
        - test_file - file name with folder prefix and without the (test|group).php suffix

Examples:
        cake testsuite app all
        cake testsuite core all

        cake testsuite app case behaviors/debuggable
        cake testsuite app case models/my_model
        cake testsuite app case controllers/my_controller

        cake testsuite core case file
        cake testsuite core case router
        cake testsuite core case set

        cake testsuite app group mygroup
        cake testsuite core group acl
        cake testsuite core group socket

        cake testsuite bugs case models/bug
          // for the plugin 'bugs' and its test case 'models/bug'
        cake testsuite bugs group bug
          // for the plugin bugs and its test group 'bug'

Code Coverage Analysis:


Append 'cov' to any of the above in order to enable code coverage analysis

As the help menu suggests, you’ll be able to run all, part, or just a single test case from your app, plugin, or core, right from the command line.

If you have a model test of test/models/my_model.test.php you’d run just that test case by running:

cake testsuite app case models/my_model