承接 matthiasnoback/symfony-dependency-injection-test 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

matthiasnoback/symfony-dependency-injection-test

Composer 安装命令:

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

包简介

Library for testing user classes related to the Symfony Dependency Injection Component

README 文档

README

By Matthias Noback and contributors

Build Status

This library contains several PHPUnit test case classes and many semantic assertions which you can use to functionally test your container extensions (or "bundle extensions") and compiler passes. It also provides the tools to functionally test your container extension (or "bundle") configuration by verifying processed values from different types of configuration files.

Besides verifying their correctness, this library will also help you to adopt a TDD approach when developing these classes.

Installation

Using Composer:

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

Usage

Testing a container extension

To test your own container extension class MyExtension create a class and extend from Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase. Then implement the getContainerExtensions() method:

<?php

use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase;

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

Basically you will be testing your extension's load method, which will look something like this:

<?php

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;

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

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

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

So in the test case you should test that after loading the container, the parameter has been set correctly:

<?php

use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase;
use PHPUnit\Framework\Attributes\Test;

class MyExtensionTest extends AbstractExtensionTestCase
{
    #[Test]
    public function after_loading_the_correct_parameter_has_been_set()
    {
        $this->load();

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

To test the effect of different configuration values, use the first argument of load():

<?php

use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase;
use PHPUnit\Framework\Attributes\Test;

class MyExtensionTest extends AbstractExtensionTestCase
{
    #[Test]
    public function after_loading_the_correct_parameter_has_been_set()
    {
        $this->load(['my' => ['enabled' => 'false']);

        ...
    }
}

To prevent duplication of required configuration values, you can provide some minimal configuration, by overriding the getMinimalConfiguration() method of the test case.

Testing a compiler pass

To test a compiler pass, create a test class and extend from Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase. Then implement the registerCompilerPass() method:

<?php

use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;

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

In each test you can first set up the ContainerBuilder instance properly, depending on what your compiler pass is expected to do. For instance you can add some definitions with specific tags you will collect. Then after the "arrange" phase of your test, you need to "act", by calling the compile()method. Finally you may enter the "assert" stage and you should verify the correct behavior of the compiler pass by making assertions about the ContainerBuilder instance.

<?php

use PHPUnit\Framework\Attributes\Test;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

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')
            ]
        );
    }
}

Standard test for unobtrusiveness

The AbstractCompilerPassTestCase class always executes one specific test - compilation_should_not_fail_with_empty_container() - which makes sure that the compiler pass is unobtrusive. For example, when your compiler pass assumes the existence of a service, an exception will be thrown, and this test will fail:

<?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');

        ...
    }
}

So you need to always add one or more guard clauses inside the process() method:

<?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');

        ...
    }
}

Use findDefinition() instead of getDefinition()

You may not know in advance if a service id stands for a service definition, or for an alias. So instead of hasDefinition() and getDefinition() you may consider using has() and findDefinition(). These methods recognize both aliases and definitions.

Test different configuration file formats

The Symfony DependencyInjection component supports many different types of configuration loaders: Yaml, XML, and PHP files, but also closures. When you create a Configuration class for your bundle, you need to make sure that each of these formats is supported. Special attention needs to be given to XML files.

In order to verify that any type of configuration file will be correctly loaded by your bundle, you must install the SymfonyConfigTest library and create a test class that extends from AbstractExtensionConfigurationTestCase:

<?php

use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionConfigurationTestCase;

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

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

Now inside each test method you can use the assertProcessedConfigurationEquals($expectedConfiguration, $sources) method to verify that after loading the given sources the processed configuration equals the expected array of values:

# in Fixtures/config.yml
twig:
    extensions: ['twig.extension.foo']
<?php // in Fixtures/config.php

return App::config([
    'twig' => [
        'extensions' => ['twig.extension.foo']
    ],
]);
<!-- in Fixtures/config.xml -->
<container>
    <twig:config>
        <twig:extension>twig.extension.bar</twig:extension>
    </twig:config>
</container>
<?php

use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionConfigurationTestCase;
use PHPUnit\Framework\Attributes\Test;

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);
    }
}

List of assertions

These are the available semantic assertions for each of the test cases shown above:

assertContainerBuilderHasService($serviceId)
Assert that the ContainerBuilder for this test has a service definition with the given id.
assertContainerBuilderHasService($serviceId, $expectedClass)
Assert that the ContainerBuilder for this test has a service definition with the given id and class.
assertContainerBuilderNotHasService($serviceId)
Assert that the ContainerBuilder for this test does not have a service definition with the given id.
assertContainerBuilderHasSyntheticService($serviceId)
Assert that the ContainerBuilder for this test has a synthetic service with the given id.
assertContainerBuilderHasAlias($aliasId)
Assert that the ContainerBuilder for this test has an alias.
assertContainerBuilderHasAlias($aliasId, $expectedServiceId)
Assert that the ContainerBuilder for this test has an alias and that it is an alias for the given service id.
assertContainerBuilderHasParameter($parameterName)
Assert that the ContainerBuilder for this test has a parameter.
assertContainerBuilderHasParameter($parameterName, $expectedParameterValue)
Assert that the ContainerBuilder for this test has a parameter and that its value is the given value.
assertContainerBuilderHasExactParameter($parameterName)
Assert that the ContainerBuilder for this test has a parameter.
assertContainerBuilderHasExactParameter($parameterName, $expectedParameterValue)
Assert that the ContainerBuilder for this test has a parameter and that its value is the given value, as well as its type matches given value type.
assertContainerBuilderHasServiceDefinitionWithArgument($serviceId, $argumentIndex)
Assert that the ContainerBuilder for this test has a service definition with the given id, which has an argument at the given index.
assertContainerBuilderHasServiceDefinitionWithArgument($serviceId, $argumentIndex, $expectedValue)
Assert that the ContainerBuilder for this test has a service definition with the given id, which has an argument at the given index, and its value is the given value.
assertContainerBuilderHasServiceDefinitionWithServiceLocatorArgument($serviceId, $argumentIndex, $expectedValue)
Assert that the ContainerBuilder for this test has a service definition with the given id, which has an argument at the given index, and its value is a ServiceLocator with a reference-map equal to the given value.
assertContainerBuilderHasServiceDefinitionWithMethodCall($serviceId, $method, array $arguments = [], $index = null)
Assert that the ContainerBuilder for this test has a service definition with the given id, which has a method call to the given method with the given arguments. If index is provided, invocation index order of method call is asserted as well.
assertContainerBuilderHasServiceDefinitionWithTag($serviceId, $tag, array $attributes = [])
Assert that the ContainerBuilder for this test has a service definition with the given id, which has the given tag with the given arguments.
assertContainerBuilderHasServiceDefinitionWithParent($serviceId, $parentServiceId)
Assert that the ContainerBuilder for this test has a service definition with the given id which is a decorated service and it has the given parent service.
assertContainerBuilderHasServiceLocator($serviceId, $expectedServiceMap)
Assert that the ContainerBuilder for this test has a ServiceLocator service definition with the given id.

Available methods to set up container

In all test cases shown above, you have access to some methods to set up the container:

setDefinition($serviceId, $definition)
Set a definition. The second parameter is a Definition class
registerService($serviceId, $class)
A shortcut for setDefinition. It returns a Definition object that can be modified if necessary.
setParameter($parameterId, $parameterValue)
Set a parameter.

Version Guidance

Version Released PHPUnit Status
6.x Aug 8, 2024 10.5, 11.5 and 12.x Latest
5.x Nov 22, 2023 9.6 and 10.x Bugfixes
4.x Mar 28, 2019 8.x and 9.x Bugfixes
3.x Mar 5, 2018 7.x Bugfixes
2.x May 9, 2017 6.x Bugfixes
1.x Jul 4, 2016 4.x and 5.x EOL

matthiasnoback/symfony-dependency-injection-test 适用场景与选型建议

matthiasnoback/symfony-dependency-injection-test 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 9.52M 次下载、GitHub Stars 达 242, 最近一次更新时间为 2013 年 09 月 04 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「dependency injection」 「Symfony2」 「phpunit」 「dev」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 matthiasnoback/symfony-dependency-injection-test 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 matthiasnoback/symfony-dependency-injection-test 我们能提供哪些服务?
定制开发 / 二次开发

基于 matthiasnoback/symfony-dependency-injection-test 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 9.52M
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 243
  • 点击次数: 33
  • 依赖项目数: 872
  • 推荐数: 0

GitHub 信息

  • Stars: 242
  • Watchers: 14
  • Forks: 49
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2013-09-04