创建模拟对象的简写

v2.4.0 2020-02-02 14:28 UTC

README

Packagist GitHub release Travis Scrutinizer Coverage Scrutinizer Packagist

厌倦了把大部分测试时间花在无休止地模拟对象上? 是的。
Moka 提供两种简单方法来减少你在这种繁琐任务上的工作量,并且通过一个令人难以置信的抽象层在最受欢迎的模拟引擎和 之间。

安装

您可以通过 composer 安装此包

composer require --dev facile-it/moka

用法

要在测试中使用 Moka,只需使用函数 Moka\Plugin\PHPUnit\moka()(见下方的生成器部分)并运行 Moka::clean() 以在每个测试之前。一个简单的接口将允许你创建 moka(模拟)对象,并通过流畅的接口用 stub 方法属性装饰它们

<?php

namespace Foo\Tests;

use Moka\Moka;
use function Moka\Plugin\PHPUnit\moka;

class FooTest extends \AnyTestCase
{
    private $foo;
    
    protected function setUp(): void
    {
        Moka::clean();
        
        // The subject of the test.
        $this->foo = new Foo(
            moka(BarInterface::class)->stub([
                // Property name => value.
                '$property' => 3,
                // Method name => return value.
                'method1' => moka(AcmeInterface::class),
                'method2' => true
            ])
        );
    }
    
    //...
}

或者,你可以直接调用 Moka::phpunit(string $fqcnOrAlias, string $alias = null): ProxyInterface,而不是使用 moka()

作为一个如此简单的项目,Moka 可以轻松集成到现有的测试套件中。

注意:如果您正在扩展 PHPUnit TestCase,为了简化清理阶段,我们提供了一个 MokaCleanerTrait,它会在每个测试之后自动运行 Moka::clean()

<?php

namespace Foo\Tests;

use Moka\Traits\MokaCleanerTrait;
use PHPUnit\Framework\TestCase;
use function Moka\Plugin\PHPUnit\moka;

class FooTest extends TestCase
{
    use MokaCleanerTrait;
    
    protected function setUp(): void
    {
        // No call to Moka::clean() needed.
        
        // ...
    }
    
    // ...
}

您可以根据下面的示例依赖原始模拟对象实现(在下面的示例中,PHPUnit 的 - 对于 Prophecy 见下文

<?php

moka(BarInterface::class, 'bar')
    ->expects($this->at(0))
    ->method('isValid')
    ->willReturn(true);

moka('bar')
    ->expects($this->at(1))
    ->method('isValid')
    ->willThrowException(new \Exception());

var_dump(moka('bar')->isValid());
// bool(true)

var_dump(moka('bar')->isValid());
// throws \Exception

参考

moka(string $fqcnOrAlias, string $alias = null): ProxyInterface

创建一个包含模拟对象(根据所选策略)的代理,用于提供的 FQCN,并且可以选择性地分配一个 $alias 来在以后获取它

<?php

$mock1 = moka(FooInterface::class); // Creates the mock for FooInterface.
$mock2 = moka(FooInterface::class); // Gets a different mock.

var_dump($mock1 === $mock2);
// bool(false)

$alias 允许您存储模拟实例

<?php

$mock1 = moka(FooInterface::class, 'foo'); // Creates a mock for FooInterface.
$mock2 = moka('foo'); // Get the mock previously created.

var_dump($mock1 === $mock2);
// bool(true)

ProxyInterface::stub(array $namesWithValues): ProxyInterface

接受一个方法或属性 stubs 的数组,格式为 [$name => $value],其中 $name 必须 是一个字符串,$value 可以是任何类型,包括另一个模拟对象。

注意:

  • 属性通过在其名称前加符号 $ 来标识
  • 当调用方法时,将抛出异常实例作为方法值
<?php

$mock = moka(BarInterface::class)->stub([
    '$property' => 1,
    'isValid' => true,
    'getMock' => moka(AcmeInterface::class),
    'throwException' => new \Exception()
]);

var_dump($mock->property);
// int(1)

var_dump($mock->isValid());
// bool(true)

注意:方法 stubs 对定义的任何方法调用都有效,并且不能被覆盖。
如果您需要更细粒度的调用策略控制,您可以通过 获取原始模拟对象实现

支持的模拟对象生成器

目前我们与内置支持 PHPUnit 模拟对象。
我们还支持其他生成器,但您需要安装相关包才能使其工作

我们为每个支持的策略提供了一个特定的 moka() 函数,以及一个静态方法(方法本身有文档说明)

  • Moka\Plugin\PHPUnit\moka
  • Moka\Plugin\Prophecy\moka
  • Moka\Plugin\Mockery\moka
  • Moka\Plugin\Phake\moka

Prophecy 本地行为

Prophecy 允许您通过直接在 ObjectProphecy 上调用方法来模拟方法。 Moka 不支持这种行为,但提供了一个简单的解决方案

<?php

// Native Prophecy behavior...
$this->prophesize(FooInterface::class)
    ->someMethod(new AnyValuesToken())
    ->willReturn($something);

// ...translates to...
Moka::prophecy(FooInterface::class)
    ->someMethod->set(new AnyValuesToken())
    ->willReturn($something);

注意:此解决方案不能用于与之前模拟的属性同名的函数

<?php

Moka::prophecy(FooInterface::class, 'foo')->stub([
    '$someName' => true
]);

var_dump(Moka::prophecy('foo')->someName);
// bool(true)

Moka::prophecy('foo')
    ->someName->set(new AnyValuesToken())
    ->willReturn($something);
// throws \Exception

插件开发

如果您觉得自己很聪明,想要创建自己的模拟生成器(或添加对现有生成器的支持),只需实现 Moka\Plugin\PluginInterface 以及相关的 Moka\Strategy\MockingStrategyInterface

<?php

namespace Moka\Plugin\YourOwn;

use Moka\Plugin\PluginInterface;
use Moka\Strategy\MockingStrategyInterface;

class YourOwnPlugin implements PluginInterface
{
    public static function getStrategy(): MockingStrategyInterface 
    {
        return new YourOwnMockingStrategy();
    }
}

通过扩展 AbstractMockingStrategy 来实现您策略的更简单(和更严格)的实现

<?php

namespace Moka\Plugin\YourOwn;

use Moka\Strategy\AbstractMockingStrategy;
use Moka\Stub\MethodStub;

class YourOwnMockingStrategy extends AbstractMockingStrategy
{
    public function __construct()
    {
        // TODO: Implement __construct() method.
    }
    
    protected function doBuild(string $fqcn)
    {
        // TODO: Implement doBuild() method.
    }
    
    protected function doDecorateWithMethod($mock, MethodStub $stub)
    {
        // TODO: Implement doDecorateWithMethod() method.
    }
    
    protected function doGet($mock)
    {
        // TODO: Implement doGet() method.
    }

    protected function doCall($mock, string $methodName)
    {
        // Override doCall() if you need special behavior.
        // See ProphecyMockingStrategy::doCall().
    }
}

注意:您的插件 FQCN 必须与模板 Moka\Plugin\YourOwn\YourOwnPlugin 匹配,其中 YourOwn 是插件名称。
您的插件和策略都必须通过我们的测试用例(请安装 phpunit/phpunit 来运行它们)

  • MokaPluginTestCase
  • MokaMockingStrategyTestCase

让我们知道任何与 Moka 相关的开发!

测试

我们强烈建议使用 Paraunit 来加快测试执行速度

composer global require facile-it/paraunit

paraunit run

致谢

许可证

MIT 许可证(MIT)。有关更多信息,请参阅 许可证文件