test.ical.ly | getting the web by the balls

Aug/10

27

Why Doctrine_Core::getTable(‘BarFoo’) is not such a good idea.. when PHPUnit testing a symfony plugin

If you have a look at the symfony and Doctrine documentation you will notice that whenever you want to get the table object for a model you will call Doctrine_Core::getTable(‘ModelName’).

Apparently this is considered a best practice however I came to thing quite the opposite.

As you probably know I am quite keen on the topic of unit testing and that is exactly where Doctrine_Core::getTable() gets annoying.

It’s a static method that will be called in many methods of your classes. Mostly actions and components I suppose but also in other places.

Now when you want to test those methods that include a static call like this you will notice that you need to bootstrap a lot of symfony and Doctrine. Many times you will also need to open a database connection. But the method you want to test only calls to database related methods.

I would like to be able to mock all these database related methods in order to optimise execution speed of my unit tests. However you can not mock static methods (with PHP3.5 and PHPUnit 3.5 you will be able to) which means that you need to connect the database.

This may all sound a bit loopy but I just spend several hours optimising unit tests replacing calls to Doctrine_Core::getTable(‘BarFoo’) with new BarFooTable().

· ·



  • caibaohua

    Hi,
    I am a beignner at PHPUnit testing. And I don’t know what is the best practices for PHPUnit testing Model class in Symfony. Can you show some codes when you are testing some database models

    Thanks

  • http://giorgiosironi.blogspot.com Giorgio Sironi

    You’re right on track for substituting the lookup with the *injection* of a Table object (hope the new is outside your classes under test). However you should “only mock types you own”, so a real Doctrine_Table object which uses a sqlite in-memory database is my standard solution.
    http://www.google.com/search?q=only+mock+types+you+own

  • Fabian

    Hi!

    I didn’t know that one cannot mock static methods. Thanks for the pointer!

    You mean PHP5.3 instead of PHP3.5 right? ;-)

  • http://christof.damian.net/ Christof

    I never checked, but does Doctrine::getTable() do anything else beside the constructor?

    I prefer normal constructors to these annoying factories anyway, because the code is easier to read and auto-completion works.

    It is also easier for QA tools to find out what is going on if you don’t rely on factories.

    I am just talking about the ones, which just wrap the ‘new’ without having any additional functionality.

    For mocking the tables I also use Giorgio’s approach, or mock my table object and make sure that I can inject it.

  • http://www.codenugget.org b00giZm

    I don’t get it..

    Am I missing something? IMHO it doesn’t really matter if you use Doctrine::getTable(‘BarFoo’) oder new BarFooTable(). If you use either one of them _inside_ the method you want to test, you’re screwed. You still have to inject it, if you want to keep your method testable.

    That’s how I do it:

    - write a protected method getBarFooTable() which acts as a wrapper around Doctrine::getTable(‘BarFoo’)
    - For testing I will extend the class I want to test and override the getBarFooTable() method, which then returns a mocked object instead of the real table object

  • Henrik

    In symfony the newer table templates include a static method “getInstance()”, this is very useful when adding “@return MyObjectTable” in the PHP docblock above to enable auto completition in the IDE. In this function the Doctrine_Core::getTable(“MyObject”) is called and like b00giZm wrote, a solution could be to create a MyObjectTableMock class and override what you need.

  • http://sebastian-bergmann.de Sebastian Bergmann

    PHPUnit 3.5 (and PHP 5.3) will not help you here as the mocking of static methods is limited to calls to static methods of the same class, not to ones in another class (such as Doctrine_Core::getTable() in your example).

  • http://www.mindworks.de/ Jörg Basedow

    My workaround is to extend sfContext:

    class myContext extends sfContext {

    public function getDoctrineTable($modelName){
    return Doctrine::getTable($modelName);
    }
    }

    and use this context in the front controller:

    require_once(dirname(__FILE__).’/../config/ProjectConfiguration.class.php’);

    $configuration = ProjectConfiguration::getApplicationConfiguration(‘frontend’, ‘prod’, false);
    sfContext::createInstance($configuration, null, ‘mwContext’)->dispatch();

    Extend sfActions (and sfComponents if needed):

    abstract class myActions extends sfActions {

    protected function getDoctrineTable($modelName) {
    return $this->getContext()->getDoctrineTable($modelName);
    }
    }

    Use this class as base class for controllers and use $this->getDoctrieneTable(‘FooBar’). instead of Doctrine::getrTable(‘FooBar’).

    When unit testing the controller inject a mock context:

    $context = $this->getMock(‘mwContext’, array(), array(), false);
    $context->expects($this->any())
    ->method(‘getDoctrineTable’)
    ->will($this->returnValue($tableMock));

    $actions = new SomeActions($context, ‘homepage’, ‘index’);

    This is still not great but it works. When using Doctrine in a plugin this might not be applicable.

  • http://test.ical.ly Christian

    @Sebatian ah bugger, I was wondering how to do that but apparently I had so much confidence in your magic. ;)

  • http://www.creatio.com.au Daniel

    I use the same method as Jörg and really like it. Overriding the context can be handy in other ways as well because you can now have non-static and non-singleton access points for your other services.

    One gotcha for this approach is in functional testing. I’ve ran in to a problem with the sfBrowser class which you’re required to pass in to sfTestFunctional. In the getContext method there is this call:

    $this->context = sfContext::createInstance($configuration);

    So if your actions rely on getContext being an instance of MyContext then they will break. Only solution I found was overriding sfBrowser with MyBrowser, implementing your own getContext method then passing an instance of MyBrowser to sfTestFunctional instead

  • http://www.dealspot.ph Jhourlad Estrella

    Can’t agree with you more. It does quite annoying, like, all the time. Why not just do a simple Doctrine_Query::create()->select()… Save you a lot of hassles.

<<

>>

Theme Design by devolux.nh2.me