PHP Unittests: Wie kann ich eine final Methode mocken?

Damit Mock-Objekte die instanceof Prüfungen im zu testenden Code passieren können, müssen sie Kinder der simulierten Klasse sein. Aber als ebendiese Kindklassen können sie keine finalen Methoden überschreiben. In diesem Blogbeitrag werde ich euch zeigen, wie ihr trotzdem sauber testen könnt.


Das Problem

Wir verfrachten unsere Job-Klasse in einen Dependency Injector (DI) und benutzen sie im weiteren Verlauf entsprechend. Ein Test für obigen Controller würde also so aussehen:

Das funktioniert zwar, aber run() wird aus der tatsächlichen Job-Klasse ausgeführt. Was zu unerwünschten Nebeneffekten führen kann. Außerdem können wir nicht überprüfen, dass run() auch tatsächlich ausgeführt wird.


Interface bietet die Lösung

Praktischerweise können wir nicht nur konkrete Klassen mit getMock() simulieren sondern auch ein Interface. Der Vorteil dabei ist, dass ein Interface per Definition keine finalen Methoden haben kann.

Wir erweitern also unseren Code um ein Interface und lassen implementieren es mit der Job-Klasse.

Das ist komplett sauberes OOP und nicht irgendein Workaround. Die instanceof Prüfung im Code muss noch entsprechend angepasst werden, sonst lässt sie die simulierte Klasse nicht mehr durch.

Der Test nutzt für getMock() jetzt das Interface und wir können zusätzlich prüfen, ob run() auch aufgerufen wird.


Aufwand für den Umbau

Natürlich sollte die Anwendung nicht für einen Test geändert werden. Wir würden also niemals eine protected Methode public machen nur um sie testen zu können. Wie ich oben aber schon erwähnt habe, ist ein Interface ganz normales OOP und auch ohne Test eine sinnvolle Erweiterung des Codes.

Ein Interface zu erstellen ist keine große Sache, mit einer guten IDE sogar nur ein paar Klicks. Die instanceof Prüfungen müssen zunächst nicht geändert werden, die Anwendung läuft genau wie vorher. Wenn wir dann durch weitere Tests, zu einer entsprechenden Methode kommen, können wir die Änderung immer noch vornehmen.

Obiges gilt natürlich nur für Legacy-Projekte, die nachträglich mit Tests verbessert werden. Wer den Gedanken von TDD (test-driven-development) lebt und somit seine Tests vor der Anwendung schreibt, muss sich hier keine Gedanken machen.


Mehr zum Thema PHP Unittests.