Loadsys

We are hiring CakePHP developers! Click here to apply.

Loadsys – CakePHP Blog

A Web Development Company Specializing in the CakePHP Framework.

Should your new CakePHP feature be a Plugin?

April 2nd, 2014

If you are working with a CakePHP application and you’re about to add a new “feature,” you may be considering making it a Plugin. There are a lot of benefits to Cake plugins, including containing the feature all in one place and making it reusable between projects. However, they aren’t always the right tool for the job. Loadsys has been developing Cake applications for over 7 years, and we’ve collectively learned a few things about when it pays off to build a plugin and when it ends up hurting you. So here are a couple things to think about that can help you decide.

  • If the new addition to the app is going to integrate with the the main app any significant way (presented in main navigation, or a CRUD stack in the admin portal for example) then it’s not a plugin; it’s a feature. Build it into the app.
  • If your addition depends on data from tables (hasOne, hasMany, HABTM) in your main app in order to work, then it’s not a plugin; it’s a part of your app. Build it into the app.
  • If your addition has its own front-end code (actions and views meant to be directly accessible), then it’s probably not a plugin; it’s a feature. Build it into the app.

When you find yourself doing anything from the list above, you are really working with a new core part of your application that should be integrated with the rest and not a stand alone reusable component. The result of these guidelines is that plugins are best used for programmer-facing features that you might want to re-use between projects: Components, Behaviors, Helpers, libraries, etc. Experience has taught us that trying to build an entire app out of plugins tends to lead to spending the majority of your time working around limits in each plugin, and figuring out creative ways to link them together while still maintaining the supposed “modularity” of each one.

On the other hand, building plugins as programmer-level tools that are never directly used by end users leverages Cake’s ability to pull in and alias things like Components, Behaviors and Helpers. It helps encourage their portability between apps and naturally maintains their loose coupling to each app. It avoids the ugliness of tying database tables to a plugin, which Cake handles relatively poorly.

As with everything there are certainly exceptions, but the times we have strayed from these guidelines have caused us many headaches over the years.

Brian Porter
Project Manager and Web Developer
Brian has a bachelor’s degree in Computer Science and has been programming for the web for over a decade. His primary interests are producing high quality code the first time, using automated testing to verify and maintain that quality and proper documentation to make it easier to sustain a project over time. His goal is always to make an application as durable as possible for the long haul. He holds a black belt in hapkido and spends as much of his free time as his wife and daughter allow playing videogames.

 

Share This

CakePHP 1.3 and Forward-Compatible Unit Tests

January 29th, 2014

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:

<?php
App::import('Model', 'MyModel');
Mock::generate('MyModel');

/**
 * MyModel Test Case
 */
class MyModelTest extends CakeTestCase {
    public function testSomeMethod() {
        $mockedMyModel = new MockMyModel();
        $mockedMyModel->returns('someMethod', 'arg1', 'expected');

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

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

<?php
App::import('Lib', 'FutureCakeTestCase.FutureCakeTestCase');
App::import('Model', 'MyModel');

/**
 * MyModel Test Case
 */
class MyModelTest extends FutureCakeTestCase {
    public function testSomeMethod() {
        $mockedMyModel = Mockery::mock('MyModel');
        $mockedMyModel->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.

Brian Porter
Project Manager and Web Developer
Brian has a bachelor’s degree in Computer Science and has been programming for the web for over a decade. His primary interests are producing high quality code the first time, using automated testing to verify and maintain that quality and proper documentation to make it easier to sustain a project over time. His goal is always to make an application as durable as possible for the long haul. He holds a black belt in hapkido and spends as much of his free time as his wife and daughter allow playing videogames.

Share This
Close
E-mail It