tweakers/symfony-service-mock

代理层,允许服务通过替换内部实现为测试替身,而不影响服务容器的(即 Symfony)绑定。

5.0.0 2023-06-29 06:24 UTC

This package is not auto-updated.

Last update: 2024-09-19 12:08:58 UTC


README

简介

这个库是为与 Symfony 的服务容器一起使用而设计的,并利用Ocramius 的代理管理器来允许你配置一个“原始”的服务实现,该实现可以随时更改为“替代”实现。

从 Symfony 4.0 开始,一旦初始化了服务,就不允许更改或删除它。因此,当你需要在单元或功能测试中使用临时测试替身时,你不能简单地替换服务。

此库允许你向 Symfony 添加一个替代配置,该配置使用特殊代理替换服务,这允许你在 Symfony 初始化服务并开始将其作为相关服务中的依赖项使用后,仍然完全控制“内部”。

安装

只需将其作为开发依赖项包含即可

composer require --dev tweakers/symfony-service-mock

如何使用

在 Symfony 中配置的任何服务都可以在测试环境特定服务的 services.yaml 中重新配置。为了使此库正常运行,你应该将这些服务重新配置为装饰器

如果你在常规配置中有一个名为 'App\TestService' 的服务,你可以这样配置它,以便允许模拟其内部行为

# In config\packages\test\services.yaml:
services:
  _defaults:
    public: true
    autowire: true

  Tweakers\Test\MockableService\MockableServiceProxyFactory: ~

  App\TestService_mocked:
    decorates: App\TestService
    decoration_inner_name: 'App\TestService.inner'
    factory: ['@Tweakers\Test\MockableService\MockableServiceProxyFactory', 'createServiceProxy']
    arguments: ['@App\TestService.inner']

仅使用此测试配置,通常不会有任何行为上的差异。尽管由于代理的使用(请参阅Ocramius 的手册)或它是公开的,可能会有一些微小的变化。

但现在所有依赖于 App\TestService 的服务都将使用新配置的装饰代理。默认情况下,对于任何实际工作都会回退到原始服务。

因此,此配置允许你调整 TestService 的行为,而无需知道使用它的服务或 Symfony 是否已经初始化了它。这可以在单元测试中这样做

<?php

namespace App;

class TestService
{
    private $stuff;

    public function getStuff()
    {
        return $this->stuff;
    }

    public function setStuff($stuff)
    {
        $this->stuff = $stuff;
    }
}
<?php

namespace App\Tests;

use App\TestService;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class TestServiceTest extends KernelTestCase
{
    protected function setUp()
    {
    	parent::setUp();

    	self::bootKernel();
    }

    protected function tearDown()
    {
        parent::tearDown();
        
        // Make sure the original version is restored inside the proxy
        self::$container->get(TestService::class)->restoreOriginalService();
    }

    public function testServiceBehaviorCanBeChanged(): void
    {
        // Set the value of "stuff" to 39 in the original TestService
        $service = self::$container->get(TestService::class);
        $service->setStuff(39);

        $this->assertSame(39, $service->getStuff());

        // Set our mock as alternative for this test
        $mock = $this->createMock(TestService::class);
        $mock->method('getStuff')->willReturn(42);
        
        $service->setAlternativeService($mock);

        $this->assertSame(42, $service->getStuff());
        
        // Revert to original service
        $service->restoreOriginalService();
        $this->assertSame(39, $service->getStuff());
    }
}

兼容性

此代码仅在 Symfony 3.4、4.1 和 4.2 上进行了测试,尽管它应该与 4.0 和更早的版本兼容。在 Symfony 4.1 中引入了TestContainer,它提供了对私有服务的访问。因此,在 Symfony 4.1 及更高版本中,可能不需要将服务定义为公开。但是,Symfony 会“内联”或完全删除未使用的私有服务(甚至在 4.1 中),因此如果将所有内容都定义为私有,测试可能会因为缺少服务而失败。为了防止内联,上面的示例创建了公共=true 的代理,但可能还需要重新定义特定的服务。