James Okpe George James Okpe George - 4 months ago 25
PHP Question

PHP: How to make testable static methods

I decided to make my own poor man's ORM framework. Since I did not know how to go about it I decide to develop before testing. Github. After making it work the way I want, I realise that my code was untestable because of the tight coupling between my static methods from the query class and BaseModel.

So I thought of some ways to make it testable:


  1. Make every query method receive a connection obect: But it means that when I try to consume these method in the BaseModel class I will still have to tightly couple the connection object to the BaseModel.

  2. Leave it the way it is: Which means during PHPUnit testing, I will have to override the BaseModel.



Whichever way, I feel that I am not doing the right thing. I believe that there is a better way I can go in making this work, and I need your help guys. Thanks

Answer

Quoting Miško Hevery in Statics are Death to Testability:

Here is another way of thinking about it. Unit-testing needs seams, seams is where we prevent the execution of normal code path and is how we achieve isolation of the class under test. seams work through polymorphism, we override/implement class/interface and than wire the class under test differently in order to take control of the execution flow. With static methods there is nothing to override. Yes, static methods are easy to call, but if the static method calls another static method there is no way to overrider the called method dependency.

Quoting Kore Nordmann in Static considered harmful:

If you start unit-testing your code you will quickly notice why static dependencies are so bad - because it becomes almost impossible to replace implementations during testing, so that you often end up testing your full framework stack or need to write dozens of helper classes just for the sake of testability. And you would not need that without static dependencies.

TL;DR: don't use statics if you want testable code.

Regarding your specific case:

If you don't want to rewrite your code to not use statics, consider adding a property for the query class on the base class, e.g. add something like

public static function setQueryClass($className) {
    static::queryClass = $queryClass;
}

and then replace all the hardcoded calls to StaticSQLQuery with the call to the set $className. While that is still kinda ugly, it would allow you to replace the StaticSQLQuery during tests with a different class, e.g. with PHP7 and Anonymous classes you'd put this into your test:

BaseClass::setQueryClass(
    get_class(new class extends StaticSQLQuery {
        public static function init()
        {
            return 'My stub';
        }
    })
);

Before PHP7 you'll have to hardcode the class instead:

class StaticSQLQueryMock extends StaticSQLQuery
{
    public static function init()
    {
        return 'My stub';
    }
}

BaseClass::setQueryClass(StaticSQLQueryMock::class);