Jak mockovat třídy, které jsou definované jako final nebo některé z jejich metod jsou final?

Mockování znamená nahrazení původního objektu za jeho testovací imitaci, která neprovádí žádnou funkci a jen se tváří jako původní objekt. A předstírá chování, které potřebujeme kvůli testování.

Takže například místo objektu PDO s metodami jako query() apod. vytvoříme jeho mock, který práci s databází jen předstírá, a místo toho ověřuje, že se volají ty správné SQL příkazy atd. Více třeba v dokumentaci Mockery.

A aby bylo možné mock předávat metodám, které mají type hint PDO, je potřeba, aby i třída mocku dědila od PDO. A to může být kámen úrazu. Pokud by totiž třída PDO nebo metoda query() byla final, už by to nebylo možné.

Existuje nějaké řešení? První možnost je final vůbec nepoužívat. To ovšem nepomůže s kódem třetích stran, který final používá, ale hlavně se tím ochuzujeme o důležitý prvek objektového návrhu. Existuje dogma, že každá třída by měla být buď final, nebo abstract.

Druhou a velmi šikovnou možností je použít Nette Tester, který od verze 2.0 disponuje vychytávkou, která odstraňuje z kódu klíčové slovo final on-the-fly. Stačí na začátku testu zavolat:

require __DIR__ . '/vendor/autoload.php';

Tester\Environment::bypassFinals();

A je to. Je za tím ukrytá neskutečně černá magie 🙂

Pokud nepoužíváte Nette Tester, ale třeba PHPUnit, nebudete ochuzeni, stačí si nainstalovat BypassFinals:

composer require dg/bypass-finals --dev

A na začátku skriptu zavoláte:

require __DIR__ . '/vendor/autoload.php';

DG\BypassFinals::enable();