PropelORM, Symfony 2 and Unit Unit

I used to write like this:

$results = SomeModelQuery::create()->filterByFoo('bar')->find();

      

However, this does not scale for unit testing because I cannot inject mock i.e. I have no control over what data is returned. I would like to use hardware data, but I cannot.

Doesn't seem like a great object application:

class Foo
{
    public __construct($someModelQuery)
    {
        $this->someModelQuery = $someMOdelQuery;
    }

    public function doSthing()
    {
         $results = $this->someModelQuery->filterByFoo('bar')->find();
    }
}

      

DI feels terrible. I have dozens of request objects to simulate and throw. Customization through the constructor is ugly and painful. Setting using a method is incorrect because it can be forgotten when called. And for every single lib and action, it always hurts to create these request objects manually.

How would I elegantly do DI with PropelORM query classes? I don't want to call a method like:

$oneQuery = OneQuery::create();
$anotherQuery = AnotherQuery::create();
// ... 10 more ...
$foo = new Foo($oneQuery, $anotherQuery, ...);
$foo->callSomeFunctionThatNeedsThose();

      

+3


source to share


1 answer


In my opinion (and Martin Folowers's ) there is a step between calling everything statically and using Dependency Injection, and that might be what you are looking for.

If I cannot do a complete DI (like Zend Framework MVC) I will use Locator. The service layer will be the place where all your classes will go to get dependencies from them. Think of it as a deep one layer abstraction for your class dependencies. There are many advantages to using Locator, but in this case I will focus on testability.

Paste the code in some code, here is the model request class

class SomeModelQuery
{
    public function __call($method, $params)
    {
        if ($method == 'find') {
            return 'Real Data';
        }
        return $this;
    }
}

      

All it does is return unless the find method is called. Then the gated string "Real data" will be returned.

Now our service locator:

class ServiceLocator
{
    protected static $instance;

    protected $someModelQuery;

    public static function resetInstance()
    {
        static::$instance = null;
    }

    public static function instance()
    {
        if (self::$instance === null) {
            static::$instance = new static();
        }
        return static::$instance;
    }

    public function getSomeModelQuery()
    {
        if ($this->someModelQuery === null) {
            $this->someModelQuery = new SomeModelQuery();
        }
        return $this->someModelQuery;
    }

    public function setSomeModelQuery($someModelQuery)
    {
        $this->someModelQuery = $someModelQuery;
    }
}

      

This does two things. Provides an instance of the global scope method, so you can always get it. Along with permission reset. We then provide get and set methods for the model request object. Lazy loaded if not already installed.



Now the code that does the real work:

class Foo
{
    public function doSomething()
    {
        return ServiceLocator::instance()
            ->getSomeModelQuery()->filterByFoo('bar')->find();
    }
}

      

Foo calls the service locator, then gets an instance of the request object from it and makes the call it needs on that request object.

So now we need to write some unit tests for all of this. Here he is:

class FooTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        ServiceLocator::resetInstance();
    }

    public function testNoMocking()
    {
        $foo = new Foo();
        $this->assertEquals('Real Data', $foo->doSomething());
    }

    public function testWithMock()
    {
        // Create our mock with a random value
        $rand = mt_rand();
        $mock = $this->getMock('SomeModelQuery');
        $mock->expects($this->any())
            ->method('__call')
            ->will($this->onConsecutiveCalls($mock, $rand));
        // Place the mock in the service locator
        ServiceLocator::instance()->setSomeModelQuery($mock);

        // Do we get our random value back?
        $foo = new Foo();
        $this->assertEquals($rand, $foo->doSomething());
    }
}

      

I gave an example where the real request code is called and where the request code is mocked.

So it gives you the ability to inject mockery with the need to inject each dependency into the classes you want to unit test.

There are many ways to write the above code. Use it as a proof of concept and adapt it to your needs.

+3


source







All Articles