php-mock / php-mock
PHP-Mock 可以模拟内置 PHP 函数(例如 time())。PHP-Mock 依赖于 PHP 的命名空间回退策略。无需进一步扩展。
Requires
- php: ^5.6 || ^7.0 || ^8.0
- phpunit/php-text-template: ^1 || ^2 || ^3 || ^4
Requires (Dev)
- phpunit/phpunit: ^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0 || ^11.0
- squizlabs/php_codesniffer: ^3.8
Suggests
- php-mock/php-mock-phpunit: Allows integration into PHPUnit testcase with the trait PHPMock.
Replaces
README
PHP-Mock 是一个测试库,可以模拟非确定性内置 PHP 函数,如 time()
或 rand()
。这是通过 PHP 的命名空间回退策略 实现的。
如果不存在命名空间函数 [...],PHP 将回退到全局函数 [...]。
PHP-Mock 通过提供命名空间函数来利用这一功能。即您必须处于 非全局命名空间 上下文,并调用未命名的函数 unqualified。
namespace foo; $time = time(); // This call can be mocked, a call to \time() can't.
要求和限制
-
只有命名空间上下文中的 未命名的 函数调用才能模拟。例如,在命名空间
foo
中对time()
的调用是可模拟的,而对\time()
的调用则不是。 -
必须在测试类中对未命名的函数的第一次调用之前定义模拟。这在 Bug #68541 中有记录。在大多数情况下,您可以忽略此限制,但如果您遇到此问题,可以在第一次调用之前调用
Mock::define()
。这将定义一个无副作用的命名空间函数,稍后可以启用。另一种有效的方法是在隔离过程中运行测试。
替代方案
如果您无法依赖或只是不想使用命名空间回退策略,还有其他模拟内置 PHP 函数的技术。
-
PHPBuiltinMock 依赖于 APD 扩展。
-
MockFunction 是一个 PHPUnit 扩展。它使用 runkit 扩展。
-
UOPZ 是一个 Zend 扩展,它允许重命名和删除函数。
-
vfsStream 是一个虚拟文件系统的流包装器。这将帮助您编写涵盖 PHP 流函数(例如
fread()
或readdir()
)的测试。
安装
使用 Composer
composer require --dev php-mock/php-mock
用法
您无需学习另一个 API。PHP-Mock 为这些测试框架提供了集成
-
php-mock/php-mock-phpunit - PHPUnit 集成
-
php-mock/php-mock-mockery - Mockery 集成
-
php-mock/php-mock-prophecy - Prophecy (phpspec) 集成
注意:如果您计划使用上述提到的任何测试框架,您可以跳过阅读下面的内容,直接查看特定的集成项目。
PHP-Mock API
您可以在 phpmock
命名空间中找到 API。
创建一个 Mock
对象。您可以使用 MockBuilder
的流畅 API 来实现。
-
MockBuilder::setNamespace()
设置被模拟函数的目标命名空间。 -
MockBuilder::setName()
设置被模拟函数的名称(例如time()
)。 -
MockBuilder::setFunction()
设置具体的模拟实现。 -
MockBuilder::setFunctionProvider()
设置,作为MockBuilder::setFunction()
的替代,将模拟实现作为FunctionProvider
-
FixedValueFunction
是一个简单的实现,总是返回相同的值。 -
FixedMicrotimeFunction
是一个简单的实现,总是返回相同的微时间戳。与FixedValueFunction
不同,它包含一个用于microtime()
的浮点数和字符串格式的转换器。 -
FixedDateFunction
是一个简单的实现,总是返回固定时间戳的格式化日期。 -
SleepFunction
是一个sleep()
实现,它不会停止但会增加一个Incrementable
,例如一个time()
模拟。 -
UsleepFunction
是一个usleep()
实现,它不会停止但会增加一个Incrementable
,例如一个microtime()
模拟。
-
-
MockBuilder::build()
构建一个Mock
对象。
在构建了您的 Mock
对象之后,您必须调用 enable()
来在给定的命名空间中启用模拟。完成模拟后,应在模拟实例上调用 disable()
来禁用它。
此示例说明了在命名空间 foo
中模拟未限定函数 time()
。
namespace foo; use phpmock\MockBuilder; $builder = new MockBuilder(); $builder->setNamespace(__NAMESPACE__) ->setName("time") ->setFunction( function () { return 1417011228; } ); $mock = $builder->build(); // The mock is not enabled yet. assert (time() != 1417011228); $mock->enable(); assert (time() == 1417011228); // The mock is disabled and PHP's built-in time() is called. $mock->disable(); assert (time() != 1417011228);
除了使用 MockBuilder::setFunction()
设置模拟函数外,还可以使用现有的 FixedValueFunction
namespace foo; use phpmock\MockBuilder; use phpmock\functions\FixedValueFunction; $builder = new MockBuilder(); $builder->setNamespace(__NAMESPACE__) ->setName("time") ->setFunctionProvider(new FixedValueFunction(1417011228)); $mock = $builder->build();
需要注意的是,setNamespace()
应该针对函数被调用的命名空间,而不是模拟它的命名空间。例如
<?php
namespace App\Code;
class Subject
{
public function foo()
{
time();
}
}
在一个测试中模拟此调用
<?php
namespace Tests\Unit;
class SubjectTest
{
public function myTest()
{
$builder = new MockBuilder();
$builder->setNamespace('\\App\\Code'); // ... etc
}
}
重置全局状态
启用的模拟会改变全局状态。如果后续的测试运行了意外调用模拟的代码,这将破坏测试。因此,你应该总是在测试用例后禁用模拟。你需要禁用创建的模拟。你可以通过调用静态方法 Mock::disableAll()
来对所有模拟执行此操作。
模拟环境
可以将几个模拟函数的复杂模拟环境组合成一个 MockEnvironment
-
MockEnvironment::enable()
启用该环境中的所有模拟函数。 -
MockEnvironment::disable()
禁用该环境中的所有模拟函数。 -
MockEnvironment::define()
定义该环境中的所有模拟函数。
SleepEnvironmentBuilder
SleepEnvironmentBuilder
构建一个模拟环境,在该环境中 sleep()
和 usleep()
返回立即。此外,它们会增加模拟的 date()
、time()
和 microtime()
的时间量。
namespace foo; use phpmock\environment\SleepEnvironmentBuilder; $builder = new SleepEnvironmentBuilder(); $builder->addNamespace(__NAMESPACE__) ->setTimestamp(1417011228); $environment = $builder->build(); $environment->enable(); // This won't delay the test for 10 seconds, but increase time(). sleep(10); assert(1417011228 + 10 == time());
如果模拟函数应该在不同的命名空间中,你可以通过 SleepEnvironmentBuilder::addNamespace()
添加更多命名空间。
间谍
Spy
使您能够访问函数调用。通过 Spy::getInvocations()
您可以访问参数和返回值。
作为 Spy
是 Mock
的一个特殊化,它的行为相同。然而,你可以省略第三个构造函数参数 callable $function
,这样就会创建一个使用现有函数的间谍。例如,new Spy(__NAMESPACE__ , "rand")
将创建一个间谍,它基本上是 PHP 内置的 rand()
的代理。
namespace foo; use phpmock\spy\Spy; function bar($min, $max) { return rand($min, $max) + 3; } $spy = new Spy(__NAMESPACE__, "rand"); $spy->enable(); $result = bar(1, 2); assert ([1, 2] == $spy->getInvocations()[0]->getArguments()); assert ($result == $spy->getInvocations()[0]->getReturn() + 3);
许可和作者
此项目免费,受WTFPL许可。项目负责人是Markus Malkusch markus@malkusch.de。这个库受到了Fabian Schmengler的文章的启发 PHP: “Mocking” built-in functions like time() in Unit Tests。
捐赠
如果您喜欢PHP-Mock,并愿意慷慨捐赠,请在这里捐赠一些比特币:1335STSwu9hST4vcMRppEPgENMHD2r1REK