Kim Prince Kim Prince - 1 year ago 42
PHP Question

PHP: Retain static methods AND maintain testability

My static methods are either of the 'helper' variety, e.g.

, or of the 'get singleton' variety, e.g.
. Either way, I am happy for them to live in a helper class.

The helper class needs to be widely available so I am loading it in my layer supertypes. Now, as far as I can see, provided that the helper can be injected into the supertypes, I have maintained full flexibility over testing my code (with the exception of the helper class itself). Does that make sense? Or am I overlooking something?

To look at it another way... it seems to me that difficulty in testing code increases in proportion to the number of calls to static methods, not in proportion to actual number of static methods themselves. By putting all these calls into one class (my helper), and replacing that class with a mock, I am testing code that is free of static calls and related problems.

(I realise that I should work towards getting rid of my Singletons, but that's going to be a longer term project).

Answer Source

In the case of a static class that is strictly a helper function like "convertToCamelCase" I would probably just have 100% coverage for that function and then consider it a "core" function and not worry about mocking it elsewhere. What is a mock for "convertToCamelCase" going to do anyway..? Perhaps your unit tests start to smell a little like integrations tests if you do it too much, but there's always a bit of a tradeoff between abstracting everything and making your app needlessly complicated.

As far as singletons it is tricky because you usually have the name of the static class in your code so it becomes problematic to swap it out with a mock object for testing. One thing you could do is wherever you are making your static method calls, start by refactoring to call them this way:

$instance = call_user_func('MyClass::getinstance');

Then, as you increase your testing coverage, you could begin replacing with something like:

$instance = call_user_func($this->myClassName . '::getinstance');

So - once you have that, you could swap out and mock MyClass by changing $this->myClassName. You'd have to make sure you're requiring or autoloading the relevant php files dynamically as well.

Refactoring to use an abstract factory pattern would make things even easier to test but you could start implementing that over time as well