Practical PHP Testing Patterns: Humble Object
Join the DZone community and get the full member experience.
Join For FreeThe problem: we have to test a component which is tied to a framework or library. This time we cannot modify its design to aid testability, for example by introducing arguments in the constructor to allow for injection. The Api has to move between the constraints of other code.
This scenario is typical for framework components: you have to extend some base class and fill in the missing pieces of logic, so that the framework may perform the rest. A common example in the PHP world is that of Zend Framework controllers, which constructor is fixed and creation is taken care by the framework's MVC stack.
This Humble Object specialization is also called Skinny controller, fat model, but it's not necessary the Model who gets fat: the controller may call a Service Layer or even further abstractions.
How to test logic in framework objects
Naturally, you want to test these object, but in them you find two kinds of logic:- A: logic you inserted, and as so should be unit-tested.
- B: Logic that was inherited or by the way featured by the framework, library, or ORM.
You should only test logic of type A, since the framework is already tested in its build for B. But how to separate them?
The Humble Object pattern solves this problem: the object the framework requires becomes an object which delegates to a Testable Component. Then:
- you test A by instantiating the Testable Component.
- B is tested by the framework's developers.
- You test the whole application wiring with few, heavy and slow end-to-end tests.
Variations
- Humble Dialog: should be called Humble View Script in PHP. Logic in the view is difficult to be tested, both for difficulties in rendering it in isolation and for difficulties in parsing the produced HTML; so we extract logic into testable components like View Helpers and just compose them. They are not only a matter of reuse but also of simple, independent testing.
- Humble Executable: in PHP may be called Humble Executable Script. Since you cannot cleanly test a .php script (including it contaminates the variable scope), you put the logic in a class and in the script just instantiate an object and call run().
- Humble Transaction Controller: extracts transaction management into an upper layer. All the code at the underlying one expects to be executed during a transaction; as a result, you can use Transaction Rollback Teardown while testing that layer.
- Humble Container Adapter: our case of framework controllers that delegate everything, because it is impossible to instantiate them independently or test them in isolation. In languages like Java, typically this is because they should be run inside a container. In our PHP case, they should be instantiated by the framework, which injects every kind of collaborator in it and manages its lifecycle like an evil J2EE container.
Example
The code sample of today cannot be a running one since I cannot bundle a Zend Framework 1 application in this article. In this example, we show the extraction of logic from a Zend Framework controller, in order to test an independent object without strange requirements.
In this scenario, a Data Access Object takes away database-related code from the controller; again, in most of the cases other layers are inserted and the controllers does not interact with these low-level objects.
<?php /** * How do we test this controller? We have to instantiate it, using a * constructor designed for the framework, and inject a lot of things like a * bootstrap object, which is a bit complex to build. Or we can mock it, * but it has lots of methods. * In fact, we can only do so with other goodies from the * framework, in our case Zend_Test. But the test will be slow since it will * be and end-to-end one. */ class PostController // extends Zend_Controller_Action { public function indexAction() { $connection = $this->bootstrap->getResource('connection'); $stmt = $connection->query('SELECT * FROM posts')->execute(); $posts = array(); foreach ($stmt->fetchAll() as $row) { $posts[] = $row; } // pass $posts to the view... } } /** * We separate the logic in an Humble Object (the controller) and the real * object which performs the work. * We can now test PostsDao in isolation, while the Humble Object short code * will be tested by very few end-to-end tests. * I don't show PostsDao implementation here for brevity reasons and because * it's really simple to grasp what goes inside: the PDO usage. */ class PostController // extends Zend_Controller_Action { public function indexAction() { $connection = $this->bootstrap->getResource('connection'); $postsDao = new PostsDao($connection); $posts = $postsDao->findAll(); // pass $posts to the view... } }
Opinions expressed by DZone contributors are their own.
Trending
-
Authorization: Get It Done Right, Get It Done Early
-
AI Technology Is Drastically Disrupting the Background Screening Industry
-
Front-End: Cache Strategies You Should Know
-
5 Key Concepts for MQTT Broker in Sparkplug Specification
Comments