Пакет помогает максимально просто имитировать внутренние функции PHP. Используйте этот пакет, когда вам нужно имитировать такие функции, как: time()
, str_contains()
, rand
и т. д.
Установка
Использование
Издевательства во время выполнения
Предопределенный макет
Смесь двух предыдущих способов
PHPUnit 9
PHPUnit 10 и выше
Зарегистрируйте расширение PHPUnit
Регистрация моков
Состояние
Отслеживание звонков
Глобальные функции в пространстве имен
Внутренняя реализация функции
Внутренние функции
Ограничения
Поставщики данных
композитору требуется xepozz/internal-mocker --dev
Основная идея довольно проста: зарегистрировать прослушиватель для PHPUnit и сначала вызвать расширение Mocker.
Создайте новый файл tests/MockerExtension.php
Вставьте следующий код в созданный файл:
<?phpdeclare(strict_types=1);пространство имен AppTests;используйте PHPUnitRunnerBeforeTestHook;используйте PHPUnitRunnerBeforeFirstTestHook;используйте XepozzInternalMockerMocker;используйте XepozzInternalMockerMockerState;последний класс MockerExtension реализует BeforeTestHook, BeforeFirstTestHook {публичная функция выполненияBeforeFirstTest(): void {$mocks = [];$mocker = new Mocker();$mocker->load($mocks); MockerState::saveState(); } Публичная функция ExecuteBeforeTest (строка $test): void { MockerState::resetState(); } }
Зарегистрируйте хук как расширение в phpunit.xml.dist
<расширения> <extension class="AppTestsMockerExtension"/> </расширения>
Создайте новый файл tests/MockerExtension.php
Вставьте следующий код в созданный файл:
<?phpdeclare(strict_types=1);пространство имен AppTests;используйте PHPUnitEventTestPreparationStarted;используйте PHPUnitEventTestPreparationStartedSubscriber;используйте PHPUnitEventTestSuiteStarted;используйте PHPUnitEventTestSuiteStartedSubscriber;используйте PHPUnitRunnerExtensionExtension;используйте PHPUnitRunnerExtensionFacade;используйте PHPUnitRunnerExtensionParameterCollection; используйте PHPUnitTextUIConfigurationConfiguration; используйте XepozzInternalMockerMocker; используйте XepozzInternalMockerMockerState; последний класс MockerExtension реализует расширение {публичная функция bootstrap (конфигурация $configuration, фасад $facade, параметрCollection $parameters): void {$facade->registerSubscribers (новый класс () реализует StartedSubscriber {public function notify(Started $event): void { MockerExtension::load(); } }, новый класс реализуетPreparationStartedSubscriber {public function notify(PreparationStarted $event): void{ MockerState::resetState(); } }, ); }публичная статическая функция load(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); MockerState::saveState(); } }
Зарегистрируйте хук как расширение в phpunit.xml.dist
<расширения> <bootstrap class="AppTestsMockerExtension"/> </расширения>
Здесь вы зарегистрировали расширение, которое будет вызываться каждый раз при запуске ./vendor/bin/phpunit
.
По умолчанию все функции будут сгенерированы и сохранены в файле /vendor/bin/xepozz/internal-mocker/data/mocks.php
.
Переопределите первый аргумент конструктора Mocker
, чтобы изменить путь:
$mocker = новый Mocker('/path/to/your/mocks.php');
Пакет поддерживает несколько способов имитации функций:
Издевательства во время выполнения
Предопределенные макеты
Смесь двух предыдущих способов
Если вы хотите, чтобы ваш тестовый пример использовался с имитируемой функцией, вам следует зарегистрировать его заранее.
Вернитесь к созданному MockerExtension::executeBeforeFirstTest
и отредактируйте переменную $mocks
.
$ издевается = [ ['namespace' => 'AppService','name' => 'time', ], ];
Этот макет будет проксировать каждый вызов time()
в пространстве имен AppService
через сгенерированную оболочку.
Если вы хотите имитировать результаты тестов, вам следует написать следующий код в необходимый тестовый пример:
MockerState::addCondition( 'AppService', // пространство имен 'время', // имя функции [], // аргументы 100 // результат);
Вы также можете использовать обратный вызов для установки результата функции:
MockerState::addCondition( '', // пространство имен 'headers_sent', // имя функции [null, null], // оба аргумента являются ссылками и еще не инициализированы при вызове функции fn (&$file, &$line) => $file = $line = 123, // результат обратного вызова);
Итак, ваш тестовый пример будет выглядеть следующим образом:
<?phpnamespace AppTests;use AppService;use PHPUnitFrameworkTestCase;класс ServiceTest расширяет TestCase {публичная функция testRun2 (): void {$service = new Service (); MockerState::addCondition('AppService','time', [],100);$this->assertEquals(100, $service->doSomething()); } }
Полный пример см. в XepozzInternalMockerTestsIntegrationDateTimeTest::testRun2
Предопределенные макеты позволяют глобально имитировать поведение.
Это означает, что вам не нужно писать MockerState::addCondition(...)
в каждый тестовый пример, если вы хотите имитировать его для всего проекта.
Имейте в виду, что одни и те же функции из разных пространств имен не одинаковы для
Mocker
.
Итак, вернемся к созданному MockerExtension::executeBeforeFirstTest
и отредактируем переменную $mocks
.
$ издевается = [ ['namespace' => 'AppService','name' => 'time','result' => 150,'arguments' => [], ], ];
После этого варианта каждый AppServicetime()
вернет 150
.
Вы можете добавить много моков. Mocker
сравнивает значения arguments
с аргументами вызывающей функции и возвращает необходимый результат.
Микс означает, что вы можете сначала использовать предварительно определенный макет , а затем — макет времени выполнения .
Если вы используете Runtime mock
вы можете столкнуться с проблемой, что после имитации функции она все равно будет имитироваться в других тестовых случаях.
MockerState::saveState()
и MockerState::resetState()
решают эту проблему.
Эти методы сохраняют «текущее» состояние и выгружают каждый примененный макет Runtime mock
.
Использование MockerState::saveState()
после Mocker->load($mocks)
сохраняет только заранее определенные макеты.
Вы можете отслеживать вызовы имитируемых функций с помощью метода MockerState::getTraces()
.
$traces = MockerState::getTraces('AppService', 'time');
$traces
будет содержать массив массивов следующей структуры:
[ ['arguments' => [], // аргументы функции'trace' => [], // результат debug_backtrace function'result' => 1708764835, // результат функции],// ... ]
Все внутренние функции заглушены для совместимости с исходными. Это заставляет функции использовать ссылочные аргументы ( &$file
), как это делают оригиналы.
Они расположены в файле src/stubs.php
.
Если вам нужно добавить новую сигнатуру функции, переопределите второй аргумент конструктора Mocker
:
$mocker = новый Mocker(stubPath: '/path/to/your/stubs.php');
Вы можете имитировать глобальные функции, отключив их в php.ini
: https://www.php.net/manual/en/ini.core.php#ini.disable-functions
Лучше всего отключить их только для тестов, выполнив команду с дополнительными флагами:
php -ddisable_functions=${functions} ./vendor/bin/phpunit
Если вы используете PHPStorm, вы можете установить команду в разделе
Run/Debug Configurations
. Добавьте флаг-ddisable_functions=${functions}
в полеInterpreter options
.
Вы можете сохранить команду в файле
composer.json
в разделеscripts
.
{ "scripts": {"test": "php -ddisable_functions=time,serialize,header,date ./vendor/bin/phpunit" } }
Замените
${functions}
списком функций, которые вы хотите имитировать, разделенными запятыми, например:time,rand
.
Итак, теперь вы также можете имитировать глобальные функции.
Когда вы отключите функцию в php.ini
вы больше не сможете ее вызывать. Это означает, что вы должны реализовать это самостоятельно.
Очевидно, что почти все функции, реализованные в PHP, выглядят так же, как и в Bash.
Самый короткий способ реализовать функцию — использовать синтаксис `bash command`
:
$mocks[] = [ 'namespace' => '', 'name' => 'time', 'function' => fn () => `date +%s`, ];
Имейте в виду, что оставление глобальной функции без реализации приведет к повторному вызову функции, что приведет к фатальной ошибке.
Иногда вы можете столкнуться с неприятной ситуацией, когда издеваемая функция не издевается без принудительного использования namespace
function
. Это может означать, что вы пытаетесь создать файл интерпретатора PHP в @dataProvider
. Будьте осторожны, и в качестве обходного пути я могу предложить вам вызвать мокер в конструкторе теста. Поэтому сначала переместите весь код из метода расширения executeBeforeFirstTest
в новый статический метод и вызовите его как в методах executeBeforeFirstTest
, так и __construct
.
последний класс MyTest расширяет PHPUnitFrameworkTestCase {публичная функция __construct(?string $name = null, массив $data = [], $dataName = '') {AppTestsMockerExtension::load();parent::__construct($name, $data, $dataName); } /// ...}
последний класс MockerExtension реализует BeforeTestHook, BeforeFirstTestHook {публичная функция выполненияBeforeFirstTest(): void {self::load(); }публичная статическая функция load(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); MockerState::saveState(); } Публичная функция выполнитьBeforeTest (строка $test): void { MockerState::resetState(); } }
И все это из-за PHPUnit 9.5 и более ранних версий системы управления событиями. Функциональность поставщика данных начинает работать раньше любых событий, поэтому невозможно имитировать функцию в начале выполнения.