Making Your App Infintely More Testable

« »

You’re writing a PHP application. You know you need to write tests. Tests aren’t something that comes naturally to you yet; you’re still working on learning the ins and outs of PHPUnit (the default standard framework). But you know that right now, your app isn’t testable. What can you do to make it testable?

The single most important thing you can do to make your app testable

Most people build applications step by step, testing them manually along the way. This isn’t a bad thing for small apps but for large apps, it’s impossible to fully test every code path, and certainly impossible to do so when releasing fixes.

This is where automated testing comes in. But still, most people don’t actually produce unit tests; they focus on functional tests. This may offer some sense of reliability, but it lacks the security of true unit testing. This is because unit testing shows you where the problem is occurring, while functional testing simply identifies that a problem exists.

But if you want to write true unit tests, you have to be able to test individual units of code (usually methods or functions). And to do this, you have to be able to remove all external dependencies like the database, the file system, the web server and HTML. And this is where everybody gets hung up with unit testing.

It’s actually easier than you think.

But it’s actually far easier than you think it is to create code that is easy to test. You only need two ingredients:

The magical combination: how it works.

Your application relies on a database connection. Most do, so you’re in good company. You want to unit test your model. But you don’t know how to decouple the database from the model without breaking everything.

“This is impossible!” you think. Not so. First, let’s implement a basic interface for your database layer. You probably do the basic four actions of a database: create, read, update and delete. So let’s have our interface do those actions:


interface Data_Storage_Interface {

    public function create($location, $data);

    public function read($location, $searchConditions, $orderBy = null, $limit = null);

    public function update($location, $data, $searchConditions);

    public function delete($location, $searchConditions);


There, we have a very (very) simple interface. This interface is by no means complete (it’s an example after all) but it does the four things a database is most likely to do. Now, we can implement this interface in the class that actually talks to the database, like so:


class Database implements Data_Storage_Interface {

Great. We’v implemented our four methods in the Database class, and we now use the interface. A curious thing about interfaces in PHP is this: the classes that implement them become a type of that interface. That means that Database is also of type Data_Storage_Interface. We can prove this if we run a simple check:

$db = new Database();
var_dump($db instanceof Data_Storage_Interface); // true

Alright, so Database is of type Data_Storage_Interface; good for it. What can we do with this?

Let’s typehint!

Limited typehinting exists in PHP. It allows you to typehint on a generic array or a specific type of object. Remember, we want to be able to test our model, so in our model constructor we’re going to typehint specifically for the interface we created.


class MyModel {
    public function__construct(Data_Storage_Interface $db) {

What benefit does this have? First, we now know for a certainty that four public methods will exist in the $db object: create(), update(), read() and delete(). But more than that: we can implement the interface we created, and if we return the expected data types back to MyModel, we can swap out the data storage class with any class we want without changing the MyModel code at all.

Let me repeat that: we can switch out the data storage API without changing MyModel at all.

How this makes your class infinitely more testable

Before, the tight coupling between MyModel and the database required that a database connection actually exist to test the class.

Now, you can get away from actually having a database connection by mocking an object (with a library like Mockery) that has the Data_Storage_Interface, but doesn’t actually connect to a database. It only has to return expected data, which removes the database dependency and ensures that your tests will effectively test the class under examination.

This is a tremendously powerful tool, not only for testing but also for your application at large. The ability to plug and play classes and objects without changing other code is the beauty of object oriented programming and one of the strongest features of PHP.

Learn more (at 20% off)

Typehinting and object oriented programming are both tough subjects. But Mastering Object Oriented PHP can help. There’s a whole chapter on typehinting, and an entire chapter on abstraction, plus over fifty real life code examples! And, between now and April 7th, the book is 20% off the list price. This offer won’t last, so get your copy now!

Brandon Savage is the author of Mastering Object Oriented PHP and Practical Design Patterns in PHP

Posted on 4/4/2013 at 8:00 am
Categories: Best Practices, Object-Oriented Development, PHP, Testing

Hikari wrote at 4/11/2013 10:15 pm:

lol I love how you talk to average programmers :D

You explained DAO without even citing the abbr!!

Indeed, you gave me motivation to build unit tests in PHP. I’ll read and learn about PHPUnit. In the long run it’s very useful to have an automated test suit.

For future articles, I’d suggest teaching how to get PHPUnit results and save it in a log file.

Devon H. O'Dell (@dhobsd) wrote at 4/12/2013 9:54 am:


Leigh Bicknell wrote at 5/2/2013 2:09 pm:

How do you get around having to pass a new Data_Storage_Interface to MyModel every time? Factory patterns, defaults, both e.g

(Data_Storage_Interface $db=Data_Storage_Interface_Factory::getInstance(Data_Storage_Interface_Factory::DATABASE))

Or something else?

Forgive my lack of technical expressions!

« »

Copyright © 2024 by Brandon Savage. All rights reserved.