该包有助于尽可能简单地模拟内部 php 函数。当您需要模拟诸如time()
、 str_contains()
、 rand
等函数时,请使用此包。
安装
用法
运行时模拟
预定义模拟
之前两种方式的混合
PHP 单元 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 {公共函数executeBeforeFirstTest(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); MockerState::saveState(); }公共函数executeBeforeTest(string $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 实现扩展 {public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void{$facade->registerSubscribers(new class () 实现 StartedSubscriber {public function notification(Started $event): void{ MockerExtension::load(); } },新类实现PreparationStartedSubscriber {public function notification(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 = new Mocker('/path/to/your/mocks.php');
该包支持几种模拟函数的方法:
运行时模拟
预定义的模拟
之前两种方式的混合
如果您想让您的测试用例与模拟函数一起使用,您应该之前注册它。
返回到创建的MockerExtension::executeBeforeFirstTest
并编辑$mocks
变量。
$mocks = [ ['命名空间'=>'AppService','名称'=>'时间', ], ];
该模拟将通过生成的包装器代理命名空间AppService
下的每次time()
调用。
当您想在测试中模拟结果时,您应该将以下代码写入所需的测试用例中:
MockerState::addCondition( 'AppService', // 命名空间 'time', // 函数名 [], // 参数 100 // 结果);
您还可以使用回调来设置函数的结果:
MockerState::addCondition( '', // 命名空间 'headers_sent', // 函数名 [null, null], // 两个参数都是引用,并且在函数调用时尚未初始化 fn (&$file, &$line) => $file = $line = 123, // 回调结果);
所以你的测试用例将如下所示:
<?phpnamespace AppTests;使用 AppService;使用 PHPUnitFrameworkTestCase;类 ServiceTest 扩展 TestCase {公共函数testRun2(): void{$service = new Service(); MockerState::addCondition('AppService','时间', [],100);$this->assertEquals(100, $service->doSomething()); } }
请参阅XepozzInternalMockerTestsIntegrationDateTimeTest::testRun2
中的完整示例
预定义的模拟允许您全局模拟行为。
这意味着如果您想模拟整个项目,则无需将MockerState::addCondition(...)
写入每个测试用例。
请记住,来自不同命名空间的相同函数对于
Mocker
来说并不相同。
所以回到创建的MockerExtension::executeBeforeFirstTest
并编辑$mocks
变量。
$mocks = [ ['namespace' => 'AppService','name' => '时间','result' => 150,'arguments' => [], ], ];
在此变体之后,每个AppServicetime()
将返回150
。
您可以添加很多模拟。 Mocker
将arguments
值与调用函数的参数进行比较并返回所需的结果。
Mix 意味着您可以首先使用预定义的模拟,然后使用运行时模拟。
如果您使用Runtime mock
您可能会面临这样的问题:在模拟函数之后,您仍然在另一个测试用例中模拟它。
MockerState::saveState()
和MockerState::resetState()
解决了这个问题。
这些方法保存“当前”状态并卸载所应用的每个Runtime mock
。
在Mocker->load($mocks)
之后使用MockerState::saveState()
仅保存预定义的模拟。
您可以使用MockerState::getTraces()
方法跟踪模拟函数的调用。
$traces = MockerState::getTraces('AppService', '时间');
$traces
将包含具有以下结构的数组数组:
[ ['arguments' => [], // 函数的参数'trace' => [], // debug_backtrace 函数的结果'result' => 1708764835, // 函数的结果],// ... ]
所有内部函数都被存根以与原始函数兼容。它使函数像原始函数一样使用引用参数( &$file
)。
它们位于src/stubs.php
文件中。
如果需要添加新的函数签名,请覆盖Mocker
构造函数的第二个参数:
$mocker = new Mocker(stubPath: '/path/to/your/stubs.php');
模拟全局函数的方法是在php.ini
中禁用它们:https://www.php.net/manual/en/ini.core.php#ini.disable-functions
最好的方法是通过运行带有附加标志的命令来仅在测试时禁用它们:
php -ddisable_functions=${函数} ./vendor/bin/phpunit
如果您使用 PHPStorm,您可以在
Run/Debug Configurations
部分设置命令。将标志-ddisable_functions=${functions}
添加到Interpreter options
字段。
您可以将该命令保留在
scripts
部分下的composer.json
文件中。
{“脚本”:{“测试”:“php -ddisable_functions =时间,序列化,标题,日期./vendor/bin/phpunit” } }
将
${functions}
替换为要模拟的函数列表,以逗号分隔,例如:time,rand
。
所以现在您也可以模拟全局函数。
当您禁用php.ini
中的函数时,您将无法再调用它。这意味着您必须自己实施它。
显然,几乎所有的功能都是用 PHP 实现的,看起来和 Bash 一样。
实现功能的最短方法是使用`bash command`
语法:
$mocks[] = [ '命名空间' => '', '名称' => '时间', '函数' => fn () => `日期 +%s`, ];
请记住,离开全局函数而不实现将导致该函数的资源调用,这将导致致命错误。
有时,当模拟函数没有模拟而不强制使用namespace
时,您可能会遇到不愉快的情况
function
。这可能意味着您正在尝试在@dataProvider
中创建 PHP 解释器文件。请小心它,作为一种解决方法,我可能建议您在测试的构造函数中调用模拟程序。因此,首先将所有代码从扩展方法executeBeforeFirstTest
移至新的静态方法,并在executeBeforeFirstTest
和__construct
方法中调用它。
最终类 MyTest 扩展了 PHPUnitFrameworkTestCase {公共函数 __construct(?string $name = null, array $data = [], $dataName = '') {AppTestsMockerExtension::load();parent::__construct($name, $data, $dataName); } /// ...}
最终类 MockerExtension 实现 BeforeTestHook、BeforeFirstTestHook {公共函数executeBeforeFirstTest(): void{self::load(); }公共静态函数load(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); MockerState::saveState(); }公共函数executeBeforeTest(string $test): void{ MockerState::resetState(); } }
这都是因为 PHPUnit 9.5 及更低版本的事件管理系统。数据提供程序功能在任何事件之前开始工作,因此不可能在运行时开始模拟该函数。