How to Mock PDO (and other) objects

Edited – Apr 25 2012: There is updated code in a later post that works with already namespaced classes.

I have been trying to wrap my head around unit-testing classes that interact with databases.  Theoretically, the unit tests should be able to run without a live server connection.

The class I was unit-testing had a method that creates a PDO connection object and stores it in a private instance property.  The problem is that you cannot get a proper PDO object without a DB connection.  The  property can be viewed and set with Reflection.  However, setting the property using reflection feels like “cheating on the test”.

The idea was that when the PDO object is created, somehow I needed to hook that class call to a mock PDO object instead.  From what I have read, this is called “Monkey Patching”.  Also according to my searching, this cannot be done in PHP.

Thanks to Namespaces in PHP 5.3+ it is actually possible.  Since I haven’t seen this technique posted elsewhere, I will demonstrate it here.


The Class Being Tested

Let’s take a look at the class.

The Test:

Here is the Test Case. It is using Simpletest, however, there is no reason why this technique can’t be used in PHPUnit (or other php testing framework).

Conclusion:

Basically, the namespace must be declared on the first line. Then, the mock PDO object is created. Using eval and string concatenation, we can declare the class we are testing within the same namespace as our mock PDO object. Monkey Patching Accomplished!!!

Caveats/Notes:

  • Namespaces must be declared on the first line of a script. However, it is possible to declare multiple namespaces afterwards in the same script. So, in the event of unit-testing one class per file, it might be best to declare a namespace without the mocked class for methods that don’t require the mock. Then, you would have a namespace for each version of the mock with different outputs. Example: One namespace for a mock that returns what it is supposed to, and then a separate namespace that defines a mock with expected error results.
  • Requires Non-Namespaced code. I have done a little bit of testing with a DBManager that is already declared within a DBManager namespace. So far I haven’t found a workaround other than string manipulation to remove the DBManager namespace. The string manipulated code would break if the class contains other namespace dependent code.
    Edit: this issue is addressed in another post.