Skip to content

OSS PHP Frameworks Unit Testing: CakePHP

Jose Diaz-Gonzalez edited this page Sep 19, 2013 · 7 revisions

CakePHP is a very popular rapid development framework for PHP, highlighting design patterns such as MVC.

Note: This is a framework that does not directly use PHPUnit to run its unit tests. While PHPUnit is the underlying framework, the tests are run via a wrapper around PHPUnit.

Source Download

Download the latest master branch from Github

Testing Documentation

The documentation to run the unit tests are found here: http://book.cakephp.org/2.0/en/development/testing.html

We followed the instructions for running the tests at the command line (see the "Running tests from command line" in the link above). Below are some additional steps we took to get the tests running correctly.

Initial Setup

In order to run the CakePHP unit tests successfully, make sure you have HHVM, Composer and other basic packages ready to go.

Some initial configuration is required for CakePHP, particularly around a sample database and dependencies.

Sample Database

A sample database is necessary to run the unit tests. We used MySQL to setup the sample database. There are many links online to help install MySQL on CentOS 6. Here is one: http://centoshelp.org/servers/database/installing-configuring-mysql-server/

Copy app/Config/database.php.default to app/Config/database.php and change the $test variable to look something like this (particular to your sample database setup):

public $test = array(
  'datasource' => 'Database/Mysql',
  'persistent' => false,
  'host' => '127.0.0.1',
  'login' => 'cakeuser',
  'password' => 'mypass',
  'database' => 'test_cakephp',
  'prefix' => '',
  //'encoding' => 'utf8',
  );

Dependencies

The CakePHP source comes with an empty vendors directory, implying that some dependencies may be needed to run the unit tests. Indeed, since CakePHP has an underlying dependency on PHPUnit, PHPUnit dependencies are needed. So, we created a composer.json file in the root CakePHP directory that looked like this:

{
  "config": {
    "vendor-dir": "vendors"
  },
  "require-dev": {
    "phpunit/phpunit": "3.7.*"
  }
}

Then we used composer to install the dependencies.

Code Changes

We are hoping this is a mistake on our end, but we had to make some code changes in order to get the tests to run correctly:

In lib/Cake/Console/cake, change the exec line to look like:

exec hhvm "$CONSOLE"/cake.php -working "$APP" "$@"

We removed -q as well from the line above.

In lib/Cake/TestSuite/CakeTestSuiteDispatcher.php, we had to change

if (is_dir($vendor . DS . 'PHPUnit')) {

to

if (is_dir($vendor . DS . 'phpunit' . DS . 'phpunit' . DS . 'PHPUnit')) {

AND

include 'PHPUnit' . DS . 'Autoload.php';

to

include 'phpunit'. DS . 'phpunit'. DS . 'PHPUnit' . DS . 'Autoload.php';

Testing

To run the CakePHP unit tests, use the following command from the root CakePHP directory (sudo may be necessary):

[sudo] lib/Cake/Console/cake test core AllTests

Fatals

String

The first fatal we were greeted to was:

HipHop Fatal error: Cannot use 'String' as class name as it is reserved in /data/users/joelm/php-open-source-projects/cakephp/lib/Cake/Utility/String.php on line 27

This fatal turns out to be a near showstopper for us. HHVM does not allow primitives to be used as class names. After trying unsuccessfully to turn HipHop syntax off via EnableHipHopSyntax=false, we went the brute force route of trying to rename String to CakeString throughout the code. This is obviously not optimal, but it got the tests moving again.

Important Note: If you go down the route of renaming the class, and use an automated system, make sure you do this from inside the lib directory to avoid changing non-git tracked files in directories like vendor.

The script we ran to make the class change looked like this:

cd /home/joelm/local/php-open-source-projects/cakephp/lib && find . -name "*.php
" -print | xargs sed -i 's/class String/class CakeString/g' && find . -name "*.p
hp" -print | xargs sed -i 's/String::/CakeString::/g' && find . -name "*.php" -p
rint | xargs sed -i 's/String()/CakeString()/g' && find . -name "*.php" -print |
 xargs sed -i 's/App::uses(\x27String\x27/App::uses(\x27CakeString\x27/g'

RegexIterator

We found out that we did not implement RegexIterator.

HipHop Fatal error: Class undefined: RegexIterator in /data/users/joelm/php-open-source-projects/cakephp/lib/Cake/Core/App.php on line 471

Autoloading

Our autoloading needs some love. We were getting stackoverflow fatals.

HipHop Fatal error: Stack overflow in /data/users/joelm/php-open-source-projects/cakephp/vendors/composer/ClassLoader.php on line 184

Here are some good issue threads that deals with this issue:

https://github.com/facebook/hiphop-php/issues/1036
https://github.com/facebook/hiphop-php/pull/959

Failures and Errors

After getting through the fatals, there were errors and failures in functionality.

preg_replace

Our implementation of preg_replace_callback() does not work well with strings that contains tags.

HipHop Notice: Undefined index: info in /data/users/joelm/php-open-source-projects/cakephp/lib/Cake/Console/ConsoleOutput.php on line 220

Here is some test code that reproduces this issue:

<?php

class MyTest {
  protected static $_styles = array(
    'error' => array('text' => 'red', 'underline' => true),
    'warning' => array('text' => 'yellow'),
  );

  public function styleText($text) {
    return preg_replace_callback(
      '/<(?P<tag>[a-z0-9-_]+)>(?P<text>.*?)<\/(\1)>/ims', array($this, '_replaceTags'), $text
    );
  }

  protected function _replaceTags($matches) {
    $x = $matches['tag'];
    return $matches['text'];
  }
}

$m = new MyTest();
//$m->styleText("Welcome to CakePHP v2.4.0 Console"); WORKS!
$m->styleText("<info>Welcome to CakePHP v2.4.0 Console</info>"); // DOESN'T WORK!

Call To Action

There are obviously some fixes that need to be made on the HHVM side in order to get the CakePHP unit tests to run correctly. However, it would be great to discuss with CakePHP core developers about possibly changing the String class to another name.

Clone this wiki locally