matthiasnoback/symfony-dependency-injection-test

用于测试与 Symfony 依赖注入组件相关的用户类的库

6.0.0 2024-04-26 08:38 UTC

README

由 Matthias Noback 和贡献者编写

Build Status

此库包含多个 PHPUnit 测试用例类以及许多语义 断言,您可以使用这些断言功能测试您的 容器扩展(或“包扩展”)和 编译器传递。它还提供了工具来通过验证不同类型配置文件的处理值来功能测试容器扩展(或“包”)配置。

除了验证其正确性之外,此库还将帮助您在开发这些类时采用 TDD 方法。

安装

使用 Composer

composer require --dev matthiasnoback/symfony-dependency-injection-test

用法

测试容器扩展

要测试您自己的容器扩展类 MyExtension,创建一个类并扩展 Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase。然后实现 getContainerExtensions() 方法

<?php

use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase;

class MyExtensionTest extends AbstractExtensionTestCase
{
    protected function getContainerExtensions(): array
    {
        return [
            new MyExtension()
        ];
    }
}

基本上,您将测试您的扩展的加载方法,其外观可能如下所示

<?php

class MyExtension extends Extension
{
    public function load(array $config, ContainerBuilder $container)
    {
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__));
        $loader->load('services.xml');

        // maybe process the configuration values in $config, then:

        $container->setParameter('parameter_name', 'some value');
    }
}

因此,在测试用例中,您应该测试在加载容器后,参数是否被正确设置

<?php

class MyExtensionTest extends AbstractExtensionTestCase
{
    /**
     * @test
     */
    public function after_loading_the_correct_parameter_has_been_set()
    {
        $this->load();

        $this->assertContainerBuilderHasParameter('parameter_name', 'some value');
    }
}

要测试不同配置值的效果,请使用 load() 的第一个参数

<?php

class MyExtensionTest extends AbstractExtensionTestCase
{
    /**
     * @test
     */
    public function after_loading_the_correct_parameter_has_been_set()
    {
        $this->load(['my' => ['enabled' => 'false']);

        ...
    }
}

为了避免重复所需的配置值,您可以通过覆盖测试用例的 getMinimalConfiguration() 方法提供一些最小配置

测试编译器传递

要测试编译器传递,创建一个测试类并扩展 Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase。然后实现 registerCompilerPass() 方法

<?php

use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase;

class MyCompilerPassTest extends AbstractCompilerPassTestCase
{
    protected function registerCompilerPass(ContainerBuilder $container): void
    {
        $container->addCompilerPass(new MyCompilerPass());
    }
}

在每个测试中,您首先需要根据编译器传递预期执行的操作正确设置 ContainerBuilder 实例。例如,您可以使用特定的标签添加一些定义,然后收集。在测试的“安排”阶段之后,您需要“行动”,通过调用 compile() 方法。最后,您可能进入“断言”阶段,并通过对该 ContainerBuilder 实例进行断言来验证编译器传递的正确行为。

<?php

class MyCompilerPassTest extends AbstractCompilerPassTestCase
{
    /**
     * @test
     */
    public function if_compiler_pass_collects_services_by_adding_method_calls_these_will_exist()
    {
        $collectingService = new Definition();
        $this->setDefinition('collecting_service_id', $collectingService);

        $collectedService = new Definition();
        $collectedService->addTag('collect_with_method_calls');
        $this->setDefinition('collected_service', $collectedService);

        $this->compile();

        $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
            'collecting_service_id',
            'add',
            [
                new Reference('collected_service')
            ]
        );
    }
}

标准无侵入性测试

AbstractCompilerPassTestCase 类始终执行一个特定测试 - compilation_should_not_fail_with_empty_container(),这确保编译器传递是无侵入性的。例如,当您的编译器传递假设服务的存在时,将抛出异常,并且此测试将失败

<?php

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class MyCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $definition = $container->getDefinition('some_service_id');

        ...
    }
}

因此,您需要在 process() 方法中始终添加一个或多个保护子句

<?php

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class MyCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition('some_service_id')) {
            return;
        }

        $definition = $container->getDefinition('some_service_id');

        ...
    }
}

使用 findDefinition() 而不是 getDefinition()

您可能无法预先知道服务 ID 是否表示服务定义,还是表示别名。因此,您可以考虑使用 hasDefinition()getDefinition() 而不是 has()findDefinition()。这些方法都识别别名和定义。

测试不同的配置文件格式

Symfony DependencyInjection 组件支持许多不同类型的配置加载器:Yaml、XML 和 PHP 文件,甚至闭包。当您为您的包创建一个 Configuration 类时,您需要确保支持这些格式中的每一种。特别需要关注 XML 文件。

为了验证任何类型的配置文件都能被您的包正确加载,您必须安装 SymfonyConfigTest 库并创建一个继承自 AbstractExtensionConfigurationTestCase 的测试类。

<?php

use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionConfigurationTestCase;

class ConfigurationTest extends AbstractExtensionConfigurationTestCase
{
    protected function getContainerExtension()
    {
        return new TwigExtension();
    }

    protected function getConfiguration()
    {
        return new Configuration();
    }
}

现在,在测试方法内部,您可以使用 assertProcessedConfigurationEquals($expectedConfiguration, $sources) 方法来验证在加载了指定的源之后,处理后的配置是否等于预期的值数组。

# in Fixtures/config.yml
twig:
    extensions: ['twig.extension.foo']
<!-- in Fixtures/config.xml -->
<container>
    <twig:config>
        <twig:extension>twig.extension.bar</twig:extension>
    </twig:config>
</container>
<?php
...

class ConfigurationTest extends AbstractExtensionConfigurationTestCase
{
    ...

    /**
     * @test
     */
    public function it_converts_extension_elements_to_extensions()
    {
        $expectedConfiguration = [
            'extensions' => ['twig.extension.foo', 'twig.extension.bar']
        ];

        $sources = [
            __DIR__ . '/Fixtures/config.yml',
            __DIR__ . '/Fixtures/config.xml',
        ];

        $this->assertProcessedConfigurationEquals($expectedConfiguration, $sources);
    }
}

断言列表

以下是在上述测试用例中每个测试案例可用的语义断言。

assertContainerBuilderHasService($serviceId)
断言测试的 ContainerBuilder 具有给定 ID 的服务定义。
assertContainerBuilderHasService($serviceId, $expectedClass)
断言测试的 ContainerBuilder 具有给定 ID 和类的服务定义。
assertContainerBuilderNotHasService($serviceId)
断言测试的 ContainerBuilder 不具有给定 ID 的服务定义。
assertContainerBuilderHasSyntheticService($serviceId)
断言测试的 ContainerBuilder 具有给定 ID 的合成服务。
assertContainerBuilderHasAlias($aliasId)
断言测试的 ContainerBuilder 具有别名。
assertContainerBuilderHasAlias($aliasId, $expectedServiceId)
断言测试的 ContainerBuilder 具有别名,并且它是给定服务 ID 的别名。
assertContainerBuilderHasParameter($parameterName)
断言测试的 ContainerBuilder 具有参数。
assertContainerBuilderHasParameter($parameterName, $expectedParameterValue)
断言测试的 ContainerBuilder 具有参数,并且其值是给定的值。
assertContainerBuilderHasExactParameter($parameterName)
断言测试的 ContainerBuilder 具有参数。
assertContainerBuilderHasExactParameter($parameterName, $expectedParameterValue)
断言测试的 ContainerBuilder 具有参数,并且其值是给定的值,同时其类型与给定值类型匹配。
assertContainerBuilderHasServiceDefinitionWithArgument($serviceId, $argumentIndex)
断言测试的 ContainerBuilder 具有给定 ID 的服务定义,该定义在给定索引处有一个参数。
assertContainerBuilderHasServiceDefinitionWithArgument($serviceId, $argumentIndex, $expectedValue)
断言测试的 ContainerBuilder 具有给定 ID 的服务定义,该定义在给定索引处有一个参数,并且其值是给定的值。
assertContainerBuilderHasServiceDefinitionWithServiceLocatorArgument($serviceId, $argumentIndex, $expectedValue)
断言测试的 ContainerBuilder 具有给定 ID 的服务定义,该定义在给定索引处有一个参数,并且其值是一个具有等于给定值的引用映射的 ServiceLocator。
assertContainerBuilderHasServiceDefinitionWithMethodCall($serviceId, $method, array $arguments = [], $index = null)
断言测试的 ContainerBuilder 具有给定 ID 的服务定义,该定义有一个对给定方法的调用,带有给定参数。如果提供了索引,则断言方法调用的调用顺序。
assertContainerBuilderHasServiceDefinitionWithTag($serviceId, $tag, array $attributes = [])
断言测试的 ContainerBuilder 具有给定 ID 的服务定义,该定义具有给定标签和给定参数。
assertContainerBuilderHasServiceDefinitionWithParent($serviceId, $parentServiceId)
断言测试的 ContainerBuilder 具有给定 ID 的服务定义,该定义是一个装饰服务,并且具有给定的父服务。
assertContainerBuilderHasServiceLocator($serviceId, $expectedServiceMap)
断言测试的 ContainerBuilder 具有给定 ID 的 ServiceLocator 服务定义。

设置容器的可用方法

在上述所有测试用例中,您可以访问一些设置容器的方

setDefinition($serviceId, $definition)
设置定义。第二个参数是Definition
registerService($serviceId, $class)
setDefinition的快捷方式。如果需要,它返回一个可修改的Definition对象。
setParameter($parameterId, $parameterValue)
设置参数。

版本指南