If you want to write unit tests against a 1.3 CakePHP application (and you should want to write unit tests), using this TestCase class and Mockery will let you use forward-compatible PHPUnit-style assertions by temporarily translating them to SimpleTest assertions. The benefit being that your tests will break less when upgrading the project to Cake 2.x.

Some Quick History

In the v1.x series of releases, CakePHP used the SimpleTest unit testing framework. The Cake project subsequently switched to PHPUnit for the 2.x release. While Cake 2.0 provided backwards-compatible wrappers for the old SimpleTest-style assertions, there was no easy way to convert any mocked objects created using SimpleTest syntax. The end result was that right after a large upgrade from Cake 1.3 to 2.0, your unit test suite would be just about as broken as your application might be. This is a bad scenario to be in, where you can’t trust your code or your unit tests.

Unfortunately now that Cake 2.x is mainstream, this state of affairs discourages developers from writing tests in any legacy 1.3 project both because of their relative uselessness immediately after a major Cake version upgrade, and due to the extra work needed to “repair” them after that upgrade.

So there are two major problems to solve to reduce the friction for developers to write tests in a legacy Cake 1.3 application:

  1. Tests need to work as well as possible immediately after the upgrade to 2.0, when they are needed most to help build trust in the upgraded version of the application.
  2. The tests themselves need to require as little attention as possible immediately following the upgrade.

Introducing the FutureCakeTestCase class

The first part of our solution to this problem was to create a compatibility layer between a developer’s testing methods and the unit test framework running underneath them. Just as the Cake core team provided backwards-compatibility methods in the 2.0 release of Cake that allowed SimpleTest-style assertions to continue to work against the PHPUnit framework being used under the hood, our new class provides PHPUnit-style assertions that developers can use in 1.3-land which are mapped to SimpleTest functions under the hood.

By sub-classing your test cases from this compatibility layer, you can use PHPUnit’s $this->assertEquals('expected', $actual) preemptively. When the time comes to upgrade the project to Cake 2, the only “cleanup” necessary for the tests is to extend from CakeTestCase again and remove the now-unnecessary App:import() lines. (This is instead of using multiple regular expressions to hunt for and replace the old SimpleTest syntax in every test file.)

The second part of the solution is to not rely on SimpleTest’s built-in mocking, which requires a lot of modification after the Cake 2 upgrade to be PHPUnit-compatible. One solution would be to try to inject PHPUnit into the 1.3 test environment, but PHPUnit is designed as its own self-supporting framework and not as a piecemeal mocking component so this seems ugly. Instead, we’ve chosen to take advantage of our FutureCakeTestCase class to make the standalone Mockery mocking framework available by default. This package works splendidly with both SimpleTest and PHPUnit, so it can serve as a bridge between the two. It allows developers to write their mocks once and not have to rewrite them for a different testing framework after upgrading to Cake 2.

An Example

Here’s an existing (and completely trivial) Cake-1.3-style test file:

returns('someMethod', 'arg1', 'expected');

        $this->assertEqual($mockedMyModel->someMethod('arg1'), 'expected');
    }
}

And here’s the same test file using the compatibility class:

shouldReceive('someMethod')
            ->with('arg1')
            ->once()
            ->andReturn('expected');

        $this->assertEquals('expected', $mockedMyModel->someMethod('arg1'));
    }
}

Using the FutureCakeTestCase

  • The Github repo is here: http://github.com/loadsys/CakePHP-FutureCakeTestCase

  • It is set up as a Cake 1.3 plugin, so either clone or submodule it into yourproject/app/plugin/future_cake_test_case.

  • Install Mockery.

  • To test that everything is working, you can execute the tests for the extended class itself:

    cake/console/cake testsuite future_cake_test_case future_cake_test_case
    
  • When you create a new test case:
    • Make the new FutureCakeTestCase class available in your test file by calling the following at the top. (Unfortunately, there isn’t a better way to do this.)
      App::import('Lib', 'FutureCakeTestCase.FutureCakeTestCase');
      
    • Extend from FutureCakeTestCase:
      MyControllerTest extends FutureCakeTestCase
      
  • Write your tests using PHPUnit assertions and Mockery mocks.

  • Run your tests like normal: cake/console/cake testsuite app all

Summary

So if you still have a Cake 1.3 application out there, and you’re interested in upgrading it to 2.0, and you know that having a good test suite will make that upgrade easier, then this plugin may be able to help you. That said, this is not a perfect solution and there are a couple trade-offs to keep in mind:

  • Mocks are in Mockery, not PHPUnit. Mockery and PHPUnit both are quite capable at mocking objects, so this isn’t necessarily bad, other than the fact that you have an extra dependency (Mockery).

  • You still have to touch your tests after upgrading to Cake 2. Every effort has been made to minimize this though, and it boils down to two project-wide search-and-replaces to remove the App::import(FutureCakeTestCase) statements and switch your test cases back to extending CakeTestCase.

  • Not all of PHPUnit’s full assertion library are available since this would require implementing big chunks of PHPUnit ourselves. All of the most common assertions are mapped though, and the others will report an assertion failure if you try to use them.

Being a new plugin means this process isn’t very well tested. I’d love to hear back about how the extended TestCase works in your Cake 1.3 project.