Lzh on GitHub

类常量

当为一个类创建测试替身时,Mockery 不会为我们正在模拟的类中定义的任何类常量创建存根。然而,有时这些类常量的缺失,加上测试设置和应用程序代码本身,可能会导致不期望的行为,甚至一个 PHP 错误:PHP Fatal error: Uncaught Error: Undefined class constant 'FOO' in ...

当为一个类创建测试替身时,Mockery 不会为我们正在模拟的类中定义的任何类常量创建存根。然而,有时这些类常量的缺失,加上测试设置和应用程序代码本身,可能会导致不期望的行为,甚至一个 PHP 错误:PHP Fatal error: Uncaught Error: Undefined class constant 'FOO' in ...

虽然在 Mockery 中支持类常量是可能的,但这需要大量的工作,而用例却很少。

命名模拟

然而,我们可以用 Mockery 支持的方式来处理这些常量——通过使用 命名模拟(Named Mocks)

命名模拟是一个测试替身,它拥有我们想要模拟的类的名称,但在其之下是一个模拟真实类的存根类,带有预设的响应。

让我们看一个虚构但并非不可能的场景:

class Fetcher
{
    const SUCCESS = 0;
    const FAILURE = 1;

    public static function fetch()
    {
        // Fetcher gets something for us from somewhere...
        return self::SUCCESS;
    }
}

class MyClass
{
    public function doFetching()
    {
        $response = Fetcher::fetch();

        if ($response == Fetcher::SUCCESS) {
            echo "Thanks!" . PHP_EOL;
        } else {
            echo "Try again!" . PHP_EOL;
        }
    }
}

我们的 MyClass 调用 FetcherFetcher 从某个地方获取一些资源——也许它从远程 Web 服务下载一个文件。我们的 MyClass 根据 Fetcher::fetch() 调用的响应打印出一条响应消息。

在测试 MyClass 时,我们并不希望 Fetcher 在我们每次运行测试套件时都去互联网上下载随机的东西。所以我们把它模拟出来:

// 使用别名:因为 fetch 是静态调用的!
\Mockery::mock('alias:Fetcher')
    ->shouldReceive('fetch')
    ->andReturn(0);

$myClass = new MyClass();
$myClass->doFetching();

如果我们运行这段代码,我们的测试会因为一个恼人的 PHP Fatal error: Uncaught Error: Undefined class constant 'SUCCESS' in ... 而报错。

这就是 namedMock() 在这种情况下如何帮助我们的。

我们为 Fetcher 类创建一个存根,为类常量创建存根,然后使用 namedMock() 创建一个基于我们存根的名为 Fetcher 的模拟:

class FetcherStub
{
    const SUCCESS = 0;
    const FAILURE = 1;
}

\Mockery::namedMock('Fetcher', 'FetcherStub')
    ->shouldReceive('fetch')
    ->andReturn(0);

$myClass = new MyClass();
$myClass->doFetching();

这之所以有效,是因为在底层,Mockery 创建了一个名为 Fetcher 的类,它扩展了 FetcherStub

即使 Fetcher::fetch() 不是一个静态依赖,同样的方法也有效:

class Fetcher
{
    const SUCCESS = 0;
    const FAILURE = 1;

    public function fetch()
    {
        // Fetcher gets something for us from somewhere...
        return self::SUCCESS;
    }
}

class MyClass
{
    public function doFetching($fetcher)
    {
        $response = $fetcher->fetch();

        if ($response == Fetcher::SUCCESS) {
            echo "Thanks!" . PHP_EOL;
        } else {
            echo "Try again!" . PHP_EOL;
        }
    }
}

而测试将会有类似这样的代码:

class FetcherStub
{
    const SUCCESS = 0;
    const FAILURE = 1;
}

$mock = \Mockery::mock('Fetcher', 'FetcherStub');
$mock->shouldReceive('fetch')
    ->andReturn(0);

$myClass = new MyClass();
$myClass->doFetching($mock);

常量映射

另一种模拟类常量的方法是使用常量映射配置。

给定一个带有常量的类:

class Fetcher
{
    const SUCCESS = 0;
    const FAILURE = 1;

    public function fetch()
    {
        // Fetcher gets something for us from somewhere...
        return self::SUCCESS;
    }
}

可以使用以下方法进行模拟:

\Mockery::getConfiguration()->setConstantsMap([
    'Fetcher' => [
        'SUCCESS' => 'success',
        'FAILURE' => 'fail',
    ]
]);

$mock = \Mockery::mock('Fetcher');
var_dump($mock::SUCCESS); // (string) 'success'
var_dump($mock::FAILURE); // (string) 'fail'