php-mock/php-mock

PHP-Mock 可以模拟内置 PHP 函数(例如 time())。PHP-Mock 依赖于 PHP 的命名空间回退策略。无需进一步扩展。

资助包维护!
michalbundyra

安装量: 11,050,650

依赖项: 101

建议者: 0

安全性: 0

星标: 358

关注者: 11

分叉: 19

公开问题: 2

2.5.0 2024-02-10 21:07 UTC

This package is auto-updated.

Last update: 2024-09-10 22:29:38 UTC


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 API

您可以在 phpmock 命名空间中找到 API。

创建一个 Mock 对象。您可以使用 MockBuilder 的流畅 API 来实现。

在构建了您的 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

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() 您可以访问参数和返回值。

作为 SpyMock 的一个特殊化,它的行为相同。然而,你可以省略第三个构造函数参数 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