Das Paket hilft dabei, interne PHP-Funktionen so einfach wie möglich nachzubilden. Verwenden Sie dieses Paket, wenn Sie simulierte Funktionen wie time()
, str_contains()
, rand
usw. benötigen.
Installation
Verwendung
Laufzeit-Mocks
Vordefinierter Mock
Mischung aus zwei bisherigen Wegen
PHPUnit 9
PHPUnit 10 und höher
Registrieren Sie eine PHPUnit-Erweiterung
Registrieren Sie Mocks
Zustand
Anrufe verfolgen
Globale Namespace-Funktionen
Interne Funktionsimplementierung
Interne Funktionen
Einschränkungen
Datenanbieter
Komponist benötigt xepozz/internal-mocker --dev
Die Grundidee ist ziemlich einfach: Registrieren Sie einen Listener für PHPUnit und rufen Sie zuerst die Mocker-Erweiterung auf.
Erstellen Sie eine neue Datei tests/MockerExtension.php
Fügen Sie den folgenden Code in die erstellte Datei ein:
<?phpdeclare(strict_types=1);namespace AppTests;verwende PHPUnitRunnerBeforeTestHook;verwende PHPUnitRunnerBeforeFirstTestHook;verwende XepozzInternalMockerMocker;verwende XepozzInternalMockerMockerState;finale Klasse MockerExtension implementiert BeforeTestHook, BeforeFirstTestHook {public functionexecuteBeforeFirstTest(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); MockerState::saveState(); }öffentliche FunktionexecuteBeforeTest(string $test): void{ MockerState::resetState(); } }
Registrieren Sie den Hook als Erweiterung in phpunit.xml.dist
<Erweiterungen> <extension class="AppTestsMockerExtension"/> </extensions>
Erstellen Sie eine neue Datei tests/MockerExtension.php
Fügen Sie den folgenden Code in die erstellte Datei ein:
<?phpdeclare(strict_types=1);namespace AppTests;verwende PHPUnitEventTestPreparationStarted;verwende PHPUnitEventTestPreparationStartedSubscriber;verwende PHPUnitEventTestSuiteStarted;verwende PHPUnitEventTestSuiteStartedSubscriber;verwende PHPUnitRunnerExtensionExtension;verwende PHPUnitRunnerExtensionFacade;verwende PHPUnitRunnerExtensionParameterCollection;verwende PHPUnitTextUIConfigurationConfiguration;verwende XepozzInternalMockerMocker;verwende XepozzInternalMockerMockerState;finale Klasse MockerExtension implementiert Erweiterung {public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void{$facade->registerSubscribers(new class () implementiert StartedSubscriber {public function notify(Started $event): void{ MockerExtension::load(); } }, neue Klasse implementiert „PreparationStartedSubscriber“ {public function notify(PreparationStarted $event): void{ MockerState::resetState(); } }, ); }public static function load(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); MockerState::saveState(); } }
Registrieren Sie den Hook als Erweiterung in phpunit.xml.dist
<Erweiterungen> <bootstrap class="AppTestsMockerExtension"/> </extensions>
Hier haben Sie eine Erweiterung registriert, die jedes Mal aufgerufen wird, wenn Sie ./vendor/bin/phpunit
ausführen.
Standardmäßig werden alle Funktionen generiert und in der Datei /vendor/bin/xepozz/internal-mocker/data/mocks.php
gespeichert.
Überschreiben Sie das erste Argument des Mocker
Konstruktors, um den Pfad zu ändern:
$mocker = new Mocker('/path/to/your/mocks.php');
Das Paket unterstützt mehrere Möglichkeiten zum Verspotten von Funktionen:
Laufzeit-Mocks
Vordefinierte Mocks
Mischung aus zwei bisherigen Wegen
Wenn Sie möchten, dass Ihr Testfall mit einer simulierten Funktion verwendet wird, sollten Sie ihn vorher registrieren.
Kehren Sie zum erstellten MockerExtension::executeBeforeFirstTest
zurück und bearbeiten Sie die Variable $mocks
.
$mocks = [ ['namespace' => 'AppService','name' => 'time', ], ];
Dieser Mock leitet jeden Aufruf von time()
unter dem Namespace AppService
über einen generierten Wrapper weiter.
Wenn Sie das Ergebnis in Tests simulieren möchten, sollten Sie den folgenden Code in den erforderlichen Testfall schreiben:
MockerState::addCondition( 'AppService', // Namespace 'time', // Funktionsname [], // Argumente 100 // Ergebnis);
Sie können auch einen Rückruf verwenden, um das Ergebnis der Funktion festzulegen:
MockerState::addCondition( '', // Namespace 'headers_sent', // Funktionsname [null, null], // beide Argumente sind Referenzen und werden beim Funktionsaufruf noch nicht initialisiert fn (&$file, &$line) => $file = $line = 123, // Rückrufergebnis);
Ihr Testfall sieht also wie folgt aus:
<?phpnamespace AppTests;AppService verwenden;PHPUnitFrameworkTestCase verwenden;Klasse ServiceTest erweitert TestCase {public function testRun2(): void{$service = new Service(); MockerState::addCondition('AppService','time', [],100);$this->assertEquals(100, $service->doSomething()); } }
Das vollständige Beispiel finden Sie in XepozzInternalMockerTestsIntegrationDateTimeTest::testRun2
Mit vordefinierten Mocks können Sie Verhalten global simulieren.
Das bedeutet, dass Sie MockerState::addCondition(...)
nicht in jeden Testfall schreiben müssen, wenn Sie ihn für das gesamte Projekt verspotten möchten.
Beachten Sie, dass dieselben Funktionen aus verschiedenen Namespaces für
Mocker
nicht gleich sind.
Kehren Sie also zum erstellten MockerExtension::executeBeforeFirstTest
zurück und bearbeiten Sie die Variable $mocks
.
$mocks = [ ['namespace' => 'AppService','name' => 'time','result' => 150,'arguments' => [], ], ];
Nach dieser Variante gibt jede AppServicetime()
150
zurück.
Sie können viele Mocks hinzufügen. Mocker
vergleicht die arguments
mit den Argumenten der aufrufenden Funktion und gibt das benötigte Ergebnis zurück.
Mix bedeutet, dass Sie zunächst den vordefinierten Mock und anschließend den Runtime-Mock verwenden können.
Wenn Sie Runtime mock
verwenden, besteht möglicherweise das Problem, dass die Funktion nach dem Mocking immer noch in anderen Testfällen verspottet wird.
MockerState::saveState()
und MockerState::resetState()
lösen dieses Problem.
Diese Methoden speichern den „aktuellen“ Status und entladen jeden angewendeten Runtime mock
-Mock.
Die Verwendung von MockerState::saveState()
nach Mocker->load($mocks)
speichert nur vordefinierte Mocks.
Sie können Aufrufe simulierter Funktionen mithilfe der Methode MockerState::getTraces()
verfolgen.
$traces = MockerState::getTraces('AppService', 'time');
$traces
enthält ein Array von Arrays mit der folgenden Struktur:
[ ['arguments' => [], // Argumente der Funktion'trace' => [], // das Ergebnis von debug_backtrace function'result' => 1708764835, // Ergebnis der Funktion],// ... ]
Alle internen Funktionen sind gestuft, um mit den Originalfunktionen kompatibel zu sein. Dadurch verwenden die Funktionen referenzierte Argumente ( &$file
), wie es die Originale tun.
Sie befinden sich in der Datei src/stubs.php
.
Wenn Sie eine neue Funktionssignatur hinzufügen müssen, überschreiben Sie das zweite Argument des Mocker
-Konstruktors:
$mocker = new Mocker(stubPath: '/path/to/your/stubs.php');
Sie können globale Funktionen verspotten, indem Sie sie in php.ini
deaktivieren: https://www.php.net/manual/en/ini.core.php#ini.disable-functions
Am besten deaktivieren Sie sie nur für Tests, indem Sie einen Befehl mit den zusätzlichen Flags ausführen:
php -ddisable_functions=${functions} ./vendor/bin/phpunit
Wenn Sie PHPStorm verwenden, können Sie den Befehl im Abschnitt
Run/Debug Configurations
festlegen. Fügen Sie das Flag-ddisable_functions=${functions}
zum FeldInterpreter options
hinzu.
Sie können den Befehl in der Datei
composer.json
im Abschnittscripts
behalten.
{ "scripts": {"test": "php -ddisable_functions=time,serialize,header,date ./vendor/bin/phpunit" } }
Ersetzen Sie
${functions}
durch die Liste der Funktionen, die Sie verspotten möchten, getrennt durch Kommas, z. B.:time,rand
.
Jetzt können Sie also auch globale Funktionen verspotten.
Wenn Sie eine Funktion in php.ini
deaktivieren, können Sie sie nicht mehr aufrufen. Das bedeutet, dass Sie es selbst umsetzen müssen.
Offensichtlich sind fast alle Funktionen in PHP implementiert und sehen genauso aus wie die Bash-Funktionen.
Der kürzeste Weg, eine Funktion zu implementieren, ist die Verwendung `bash command`
-Syntax:
$mocks[] = [ 'namespace' => '', 'name' => 'time', 'function' => fn () => `date +%s`, ];
Bedenken Sie, dass das Verlassen einer globalen Funktion ohne Implementierung zu einem Regress-Aufruf der Funktion führt, der zu einem schwerwiegenden Fehler führt.
Manchmal kann es zu einer unangenehmen Situation kommen, wenn eine verspottete Funktion ohne erzwungene Verwendung namespace
nicht verspottet wird
function
. Dies kann bedeuten, dass Sie versuchen, eine PHP-Interpreterdatei in @dataProvider
zu erstellen. Seien Sie vorsichtig und als Workaround empfehle ich Ihnen möglicherweise, den Spott im Testkonstruktor aufzurufen. Verschieben Sie also zunächst den gesamten Code von Ihrer Erweiterungsmethode executeBeforeFirstTest
“ in die neue statische Methode und rufen Sie ihn sowohl in den Methoden executeBeforeFirstTest
als auch __construct
auf.
Die letzte Klasse MyTest erweitert PHPUnitFrameworkTestCase {öffentliche Funktion __construct(?string $name = null, array $data = [], $dataName = '') {AppTestsMockerExtension::load();parent::__construct($name, $data, $dataName); } /// ...}
Die letzte Klasse MockerExtension implementiert BeforeTestHook und BeforeFirstTestHook {public functionexecuteBeforeFirstTest(): void{self::load(); }public static function load(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); MockerState::saveState(); }öffentliche FunktionexecuteBeforeTest(string $test): void{ MockerState::resetState(); } }
Das alles liegt an PHPUnit 9.5 und einem niedrigeren Event-Management-System. Die Funktionalität des Datenanbieters beginnt vor allen Ereignissen zu funktionieren, daher ist es unmöglich, die Funktion zu Beginn der Laufzeit zu simulieren.