happyr/service-mocking

简化在构建容器中模拟服务的操作

资助包维护!
Nyholm

安装: 150 029

依赖项: 2

建议者: 0

安全性: 0

星标: 43

关注者: 5

分支: 7

开放问题: 1

类型:symfony-bundle

0.3.1 2022-07-07 05:29 UTC

This package is auto-updated.

Last update: 2024-09-07 10:04:15 UTC


README

Latest Version Total Downloads

您希望测试尽可能快地运行,因此您只需构建一次容器,然后让所有测试在该构建的容器上运行。这真是太好了!

然而,当您的服务容器构建时,它是不可变的。当您希望在功能测试期间模拟服务时,这会引发问题。您无法更改服务容器中的对象。

使用此包,您可以标记一些服务为“可模拟”,这将允许您为该服务中的方法定义新的自定义行为。如果没有定义自定义行为,服务将按正常工作。

安装

composer require --dev happyr/service-mocking

请确保只为您的测试环境启用此包

// config/bundles.php

<?php

return [
    // ...
    Happyr\ServiceMocking\HappyrServiceMockingBundle::class => ['test' => true],
];

配置服务

您需要告诉包您想要模拟哪些服务。这可以通过“happyr_service_mock”服务标签或通过定义服务ID列表来实现

PHP配置(Symfony 5.3)
<?php
// config/packages/test/happyr_service_mocking.php

use Symfony\Config\HappyrServiceMockingConfig;

return static function (HappyrServiceMockingConfig $config) {
    $config->services([
        \App\AcmeApiClient::class
        \App\Some\OtherService::class
    ]);
};
Yaml配置
# config/packages/test/happyr_service_mocking.yaml

happyr_service_mocking:
    services:
        - 'App\AcmeApiClient'
        - 'App\Some\OtherService'

使用方法

use App\AcmeApiClient;
use App\Some\OtherService;
use Happyr\ServiceMocking\ServiceMock;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class MyTest extends WebTestCase
{
    public function testFoo()
    {
        // ...

        $apiClient = self::getContainer()->get(AcmeApiClient::class);
        // On Symfony < 5.3
        // $apiClient = self::$container->get(AcmeApiClient::class);

        // For all calls to $apiClient->show()
        ServiceMock::all($apiClient, 'show', function ($id) {
            // $id here is the same that is passed to $apiClient->show('123')
            return ['id'=>$id, 'name'=>'Foobar'];
        });

        // For only the next call to $apiClient->delete()
        ServiceMock::next($apiClient, 'delete', function () {
            return true;
        });

        // This will queue a new callable for $apiClient->delete()
        ServiceMock::next($apiClient, 'delete', function () {
            throw new \InvalidArgument('Item cannot be deleted again');
        });

        $mock = // create a PHPUnit mock or any other mock you want.
        ServiceMock::swap(self::getContainer()->get(OtherService::class), $mock);

        // ...
        self::$client->request(...);
    }

    protected function tearDown(): void
    {
        // To make sure we don't affect other tests
        ServiceMock::resetAll();
        // You can include the RestoreServiceContainer trait to automatically reset services
    }
}

内部

那么这个魔法是如何工作的呢?

当容器构建时,会根据您的服务定义生成一个新的代理类。代理类的行为和原始类一样。但在每次方法调用时,它会检查ProxyDefinition是否已添加自定义行为。

通过静态属性的帮助,即使Kernel重启,ProxyDefinition也会被记住。

限制

如果您有两个不同的PHP进程,即您使用Panther、Selenium等运行测试,则此技巧将不起作用。

如果您的服务是final的,我们也不能创建代理。

我们只能模拟对服务的直接访问。间接方法调用不会被模拟。例如

class MyService {
    public function foo()
    {
        return $this->bar();
    }

    public function bar()
    {
        return 'original';
    }
}

如果我们将MyService::bar()模拟为返回“mocked”。当您调用MyService::foo()时,您仍然会得到“original”。解决方法是模拟MyService::foo()