Dependency injection

Chronicles of a build – the signup plugin 09

Small and nice but not testable

This code here is from one of the plugin classes (I’ve left significant code only) I’ve broken my plugin’ two moloch classes into and, sadly, it’s not testable

class membersignup_Redirect_Controller { protected static $instance = null; public static function get_instance() { // If the single instance hasn't been set, set it now. if ( null == self::$instance ) { self::$instance = new self(); } return self::$instance; } private function __construct(){ $this->globals = adclasses_Globals::get_instance(); $this->filters->add_action( 'wp_loaded', array( $this, 'redirect_to_member_login' ) ); } public function some_function(){ if ( $this->globals->pagenow == 'foo' ) return; } } 

Even if I trust the implementation of the singleton pattern and will not test __construct and get_instance I can’t test some_function: why?
some_function uses a property the class has instantiated in its constructor, globals, that I have no way to intercept, modify or watch.

Injecting testability

One big class vs. many small classes
One big class vs. many small classes

In the picture above the two softwares so the same but the second one is way more testable both at intergration level and at unit level. The big difference between the two is that, in the second scenario, I can do this
Dependency injection
Dependency injection

Making my code testable

I make a small modification to the code above to make it testable:

class membersignup_Redirect_Controller { protected static $instance = null; // here I can inject my objects into the code and now the constructor will // use those in place of those it has autonomously generated // since I default `args` to `null` not passing them will not cause any harm public static function get_instance( $args = null ) { // If the single instance hasn't been set, set it now. if ( null == self::$instance ) { self::$instance = new self( $args ); } return self::$instance; } // here too I made the injection possible // my code entry point is the `get_instance` method but // it will call `__construct` at least once // here too passing no `args` will not cause any harm private function __construct( $args = null ){ $this->filters = null; $this->globals = null; // if no args are provided then go and init the properties if ( ! is_array($args) ){ $this->filters = adclasses_Filters::get_instance(); $this->globals = adclasses_Globals::get_instance(); } // if args are provided then use those instead // this is the injection part of the code else{ $this->filters = $args['filters']; $this->globals = $args['globals']; } $this->filters->add_action( 'wp_loaded', array( $this, 'redirect_to_member_login' ) ); } // this method is needed to force `get_instance` to re-call // `__construct` later to inject new objects in it public static function unset_instance(){ if ( null != self::$instance ) self::$instance = null; } public function some_function(){ if ( $this->globals->pagenow == 'foo' ) return; } } 

Now I can specify the values that will be assigned to the object properties and can hence control them and, during unit-testing, mock them to check and watch the program flow. This is nothing new to the OOP scene and it’s called dependency injection.
Having applied the same principle to all my plugin classes my code is now testable the way I want it.

Disclaimer

What I write in these posts is not the perfect travel of an expert and consumed WordPress developer but the gnarly and error-prone stroll of an average developer (pun intended).
I will make mistakes and will try to correct them along the road and mean to share the path I’ve taken with all its pitfalls and wrong turns and not to show the best possible one.

Follow along

After setting up the plugin using grunt-init I’ve deployed it in my local site and made it available on GitHub. I will commit to the GitHub repository throughout the work and the plugin can be downloaded and installed in WordPress. The code I show becomes much more comprehensible when observed in context.

I appreciate your input