"Test Injection" design pattern

published by yegor256 on Nov 24, 2009

Environment dependencies

Consider the example, your application is required a user to be logged in before doing any manipulations with data. You have implemented a password validator, for example:

<?php
class Model_User
{
    public function 
isValidPassword($password)
    {
        return 
$this->getPassword() == $password;
    }
}

When developing this application in your local environment you should create a new user in the database and setup a password. Imagine the situation when method getPassword() is time-consuming and depends on, say, LDAP. You don't have access to production LDAP server and you need to put a temporary stub into your validator:

<?php
class Model_User
{
    public function 
isValidPassword($password)
    {
        
// user will be able to login with any password
        // in testing and development environments
        
if (APPLICATION_ENV != 'production')
            return 
true;
        return 
$this->getPassword() == $password;
    }
}

When application is quite big and depends on many third-party resources around it, there will be many such environment dependent conditions inside the code. And it will become more and more difficult to manage them.

"Test Injection", the idea

There is another approach, which we can call "test injection". You move all environment dependencies outside your code and inject them right the same way you inject dependencies. You should create a separate class, instantiated and executed in your bootstrap:

<?php
class Bootstrap extends FaZend_Application_Bootstrap_Bootstrap
{
    protected function 
_initTestInjector()
    {
        require_once 
'test/injector/Injector.php';
        
$injector = new Injector();
        
$injector->inject();
    }
}

This class will configure all your other classes, by means of injecting specific data and behavior:

<?php
class Injector extends FaZend_Test_Injector
{
    protected function 
_injectLogin()
    {
        
// don't validate user passwords
        
Model_User::setPasswordIsValidated(false);
    }
}

Your user password validator now will look differently, more clear and environment independent:

<?php
class Model_User
{
    protected static 
$_passwordIsValidated true;
    public static function 
setPasswordIsValidated($is true)
    {
        
self::$_passwordIsValidated $is;
    }
    public function 
isValidPassword($password)
    {
        if (!
self::$_passwordIsValidated)
            return 
true;
        return 
$this->getPassword() == $password;
    }
}

Now all your environment dependencies are located in one single class Injector and you can control behavior of your application in one place. Instead of spreading if-conditions through the whole project.

Moreover, Injector class will be executed before any unit test (since it's called in your bootstrap) and will become a "global" setUp() method.

Copyright Notice: The article is published by FaZend.com and is protected by US and International copyright laws. You may not republish, copy, reproduce or distribute this article or its paragraphs or elements. You may reference the article in your documentation with a mandatory notice about the authorship of the material. If you have any other privacy concerns about the materials published on FaZend.com website you shall email to privacy@fazend.com.


read all articles...