Saturday, November 07, 2009

Questions on controllers testing

A different kind of controllerDavid Weinraub has written to me asking some basic questions about my last post on dropping unit testing for controllers. I am glad to write a less technically advanced post to answer these questions for the readers that experience difficult in following the discussion in the previous post. I guess it can be helpful for other developers that have just started exploring the possibilities of good object-oriented programming.
Let's start with the questions.
On your recent post How to not test controllers, you have the following code in a controller:
$repository = $this->application->factory->getUserRepository();
Was this what you meant in our previous discussion by using a single app-wide factory for creating your service objects? The factory with approx 100 calls to "new"?
In my opinion pure factories should have nearly no logic: they only create objects and their responsibility resides here. Though, in my approach only the controllers know that a factory exists.
So you create this $factory at some early phase, bootstrap I guess, and then "store" it as an attribute on the $application object? Presumably the $application object is always accessible wherever I am in the app, so if I need some service, either to use directly or to pass as a collaborator to some other service (like a repository or a mailer), I can get it using code like the above.
The container which provides the factory is not important, but in this case is the Zend Framework bootstrap object or a Zend_Application instance, the only things that the infrastructure allows us to inject into controllers. It would be great, alternatively, to inject only necessary services in the controller, having also the controller object created by the factory (but it's not supported out of the box in zd).
Note that for easiness of decoupling and testing, this choice is bad: every time we break the Law of Demeter in a class its tests will increase in size and complexity. Normally I want to avoid these violations but since I decided to treat the controllers only as wiring code and not to unit test them it's not a problem. All my business logic is kept in the underlying domain layer if it is domain related or in action and view helpers if it is focused on data formats like html and json, or other cross-cutting concerns; these classes are then extensively tested and writed via TDD. I test controllers only in integration, and their responsibility is to act as a dumb bridge between requests/view variables and the Domain Model. Controllers are only very simple mediators in my applications: I would unit test them if I do not have to import third-party libraries to inject their collaborators, because their Api will improve.
Perhaps the factory itself calls it's own methods when it needs to create more complicated services using collaborator services?
I do this all the time. It's stupid to not reuse code :) However, it is usually simpler to expose only the narrow set of end-user services: for instance my Reflector, which gathers domain classes metadata, is used only by other services and so it is created in a protected method. Only the services which are requested directly by controllers earn a public method.
Does this factory maintain an internal registry of the objects it has created, so that subsequent calls to the factory do not produce duplicates of the objects? Or alternatively, is it is "dumb" factory, so that each call to, say, getUserRepository() creates a new one. I can't imagine you allow your factory to make duplicates like that.
If you want, you can cache them in the factory field references, and even unit test the factory by requesting the same service twice and executing assertSame() on the objects returned. In php it's not very useful: how many times a service is requested two times from the factory, by the same controller? It makes a lot of sense in Java where objects are not garbage-collected between http requests and can thus be shared. It is also necessary in php mainly when a collaborator must be shared between different services.
The nice caveat is that if you implement consistently this caching also the smaller factories (which create objects with shorter lifetimes like View Helpers) will be cached as they are instanced once in the main factory and then shared. This is true with every level of nested factories you reach and, without using singletons, you will never need to duplicate an object when not needed.

I guess I have clarifyied why I am abandoning unit testing for controllers and making them break the Law of Demeter, without feeling remorse or renouncing to a good design. Feel free to post comments to continue the discussion.
The image at the top is a photograph of a SNES controller. This is a pun and not the controller we are talking about.

1 comment:

David Weinraub said...

Hi Giorgio,

Many thanks for the explanations. Very helpful.

I hope that downshifting like this doesn't give whiplash to all the high-speed drag racers out there. But as you note, for those of us who are just beginning to explore the possibilities of good OO programming, addressing some of these smaller points is quite helpful.

Again, many thanks. Cheers,

ShareThis