Skip to content
Advertisement

Unit Testing (PHP): When to fake/mock dependencies and when not to

is it better to fake dependencies (for example Doctrine) for unit-tests or to use the real ones?

Advertisement

Answer

In a unit test, you use only ONE real instance of a class, and that is the class that you want to test.

ALL dependencies of that class should be mocked, unless there is a reason not to.

Reasons not to mock would be if data objects are being used that have no dependencies itself – you can use the real object and test if it received correct data afterwards.

Another reason not to mock would be if the configuration of the mock is too complicated – in that case, you have a reason to refactor the code instead, because if mocking a class is too complicated, the API of that class might be too complicated, too.

But the general answer: You want to always mock every dependency, every time.

I’ll give you an example for the “too-complicated-so-refactor” case.

I was using a “Zend_Session_Namespace” object for internal data storage of a model. That instance got injected into the model, so mocking was not an issue.

But the internal implementation of the real “Namespace” class made me mock all the calls to __set and __get in the correct order of how they were used in the model. And that sucked. Because every time I decided to reorder the reading and writing of a value in my code, I had to change the mocking in the tests, although nothing was broken. Refactoring in the code should not lead to broken tests or force you to change them.

The refactoring added a new object that separates the “Zend_Session_Namespace” from the model. I created an object that extends “ArrayObject” and contains the “Namespace”. On creation, all the values got read from the Namespace and added to the ArrayObject, and on every write, the value also gets passed to the Namespace object as well.

I now had the situation that I could use a real extended ArrayObject for all my tests, which in itself only needed an unconfigured mocked instance of “Zend_Session_Namespace”, because I did not need to test whether the values were correctly stored in the session when I tested the model. I only needed a data store that gets used inside the model.

To test that the session gets correctly read and written, I have tests for that ArrayObject itself.

So in the end I am using a real instance of the model, and a real instance of the data store together with a mocked instance of “Zend_Session_Namespace” which does nothing. I deliberately chose to separate “model stuff” and “session save stuff” which had been mixed into the model class before -> “single responsibility principle”.

The testing really got easier that way. And I’d say that this is also a code smell: If creating and configuring the mock classes is complicated, or needs a lot of changes when you change the tested class, it is time to think about refactoring. There is something wrong there.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement