【问题标题】:Symfony2: Testing entity validation constraintsSymfony2:测试实体验证约束
【发布时间】:2011-10-23 16:37:53
【问题描述】:

有没有人在 Symfony2 中对实体的验证约束进行单元测试的好方法?

理想情况下,我希望能够访问单元测试中的依赖注入容器,这将使我能够访问验证器服务。拥有验证器服务后,我可以手动运行它:

$errors = $validator->validate($entity);

我可以扩展WebTestCase,然后根据文档创建一个client 以访问容器,但感觉不对。文档中的 WebTestCase 和 client 更多地是用于测试动作作为一个整体,因此使用它来对实体进行单元测试感觉很糟糕。

那么,有谁知道如何 a) 获取容器或 b) 在单元测试中创建验证器?

【问题讨论】:

  • 我不确定选择哪个帖子作为下面的答案,因为我认为两者都是正确的。我会自己打勾(如果没有人反对),因为它只关注验证器,但马特的回答允许整个 DIC 对某些人有用(我在原始问题中提到过)。
  • 选择两个答案中的任何一个,没有问题 :) 如果它可以帮助其他人,我很高兴,也很高兴你确实解决了你的问题 :)

标签: symfony


【解决方案1】:

好的,因为这有两票,我想其他人有兴趣。

我决定把我的铲子拿出来,并惊喜地发现(到目前为止)这并不难实现。

我记得每个 Symfony2 组件都可以在独立模式下使用,因此我可以自己创建验证器。

查看文档:https://github.com/symfony/Validator/blob/master/ValidatorFactory.php

我意识到,因为有一个 ValidatorFactory,所以创建一个验证器是微不足道的(特别是对于我这样通过注释完成的验证,尽管如果你查看我上面链接的页面上的 docblock,你也会找到验证的方法xml 和 yml)。

第一:

# Symfony >=2.1
use Symfony\Component\Validator\Validation;
# Symfony <2.1
use Symfony\Component\Validator\ValidatorFactory;

然后:

# Symfony >=2.1
$validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();
# Symfony <2.1
$validator = ValidatorFactory::buildDefault()->getValidator();

$errors = $validator->validate($entity);

$this->assertEquals(0, count($errors));

我希望这对良心不允许他们只使用 WebTestCase 的其他人有所帮助;)。

【讨论】:

  • ValidatorFactory 在 Symfony 2.1 中已被弃用。如果你使用注解,你可以用 Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator() 替换它。
  • 感谢您的更新,确实这个答案在某种程度上已经过时了 Symfony2.1。
  • 太棒了!正是我想要的。谢谢!
  • 并非所有服务都那么容易构建——它们可能需要其他服务或(可能需要处理的配置变量)。 IMO 最好使用容器。
  • 对于 Symfony 4.2,只需将断言替换为 $this-&gt;assertEquals(0, $errors-&gt;count());
【解决方案2】:

我们最终会滚动您自己的基础测试用例,以从测试用例中访问依赖项容器。这里有问题的类:

<?php

namespace Application\AcmeBundle\Tests;

// This assumes that this class file is located at:
// src/Application/AcmeBundle/Tests/ContainerAwareUnitTestCase.php
// with Symfony 2.0 Standard Edition layout. You may need to change it
// to fit your own file system mapping.
require_once __DIR__.'/../../../../app/AppKernel.php';

class ContainerAwareUnitTestCase extends \PHPUnit_Framework_TestCase
{
    protected static $kernel;
    protected static $container;

    public static function setUpBeforeClass()
    {
        self::$kernel = new \AppKernel('dev', true);
        self::$kernel->boot();

        self::$container = self::$kernel->getContainer();
    }

    public function get($serviceId)
    {
        return self::$kernel->getContainer()->get($serviceId);
    }
}

有了这个基类,您现在可以在测试方法中执行此操作来访问验证器服务:

$validator = $this->get('validator');

我们决定使用静态函数而不是类构造函数,但您可以轻松更改行为以将内核直接实例化到构造函数中,而不是依赖 PHPUnit 提供的静态方法 setUpBeforeClass。

另外,请记住,您的测试用例中的每个测试方法都不会相互隔离,因为容器是为整个测试用例共享的。对容器进行修改可能会对您的其他测试方法产生影响,但如果您仅访问 validator 服务,则不应出现这种情况。但是,这样一来,测试用例会运行得更快,因为您不需要为每个测试方法实例化和启动新内核。

为了参考起见,我们从blog post 中找到本课程的灵感。它是用法语写的,但我更喜欢它属于谁:)

问候,
马特

【讨论】:

  • 啊太棒了!谢谢你的回复——当我在下面写我的时候,这个由 ajax 弹出,如果我先刷新我的页面会有所帮助;)。这看起来也是一个很好的解决方案,特别是如果您需要访问的不仅仅是容器上的验证器。此外,您在下面的代码中提出了一个很好的观点,即验证器可以作为实例变量保存,而不是为了性能而为每个测试方法重新创建。再次感谢你! :)
  • 请注意,引导整个内核可能会很繁重,并且可能会大大减慢您的测试速度。谨慎使用。
  • 对我来说,这个类需要声明abstract,否则PHPUnit认为它是一个测试类(即使我把它移出Tests目录并从名称中去掉Test – 我认为这是因为它扩展了 PHPUnit_Framework_TestCase...)
  • @Tim 可能是因为缺少/不正确的 PHPUnit 配置:symfony.com/doc/current/book/testing.html#phpunit-configuration
  • Symfony 3 : 为此类测试创建了一个新类:KernelTestCase
【解决方案3】:

我喜欢 Kasheens 的回答,但它不再适用于 Symfony 2.3。 变化不大:

use Symfony\Component\Validator\Validation;

和

$validator = Validation::createValidatorBuilder()->getValidator();

例如,如果您想验证注释,请使用 enableAnnotationMapping(),如下所示:

$validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();

其余的保持不变:

$errors = $validator->validate($entity);
$this->assertEquals(0, count($errors));

【讨论】:

  • 官方使用链接github.com/symfony/Validator/blob/master/README.md#usage;使用 Symfony\Component\Validator\Validation;使用 Symfony\Component\Validator\Constraints\Length; $validator = 验证::createValidator(); $violations = $validator->validateValue('Bernhard', new Length(array('min' => 10)));
  • 我真的不知道为什么 Symfony Forms 的官方单元测试文档中没有显示这个!感谢您指出此解决方案。
【解决方案4】:

使用 Symfony 2.8,您现在似乎可以通过这种方式使用 AbstractConstraintValidatorTest 类:

<?php
namespace AppBundle\Tests\Constraints;

use Symfony\Component\Validator\Tests\Constraints\AbstractConstraintValidatorTest;
use AppBundle\Constraints\MyConstraint;
use AppBundle\Constraints\MyConstraintValidator;
use AppBundle\Entity\MyEntity;
use Symfony\Component\Validator\Validation;

class MyConstraintValidatorTest extends AbstractConstraintValidatorTest
{
    protected function getApiVersion()
    {
        return Validation::API_VERSION_2_5;
    }

    protected function createValidator()
    {
        return new MyConstraintValidator();
    }

    public function testIsValid()
    {
        $this->validator->validate(null, new MyEntity());

        $this->assertNoViolation();
    }

    public function testNotValid()
    {
        $this->assertViolationRaised(new MyEntity(), MyConstraint::SOME_ERROR_NAME);
    }
}

IpValidatorTest class 有一个很好的样本

【讨论】:

  • 第一个 use 中的小错字。应该是 Tests 而不是 Test
【解决方案5】:

https://stackoverflow.com/a/41884661/4560833 中的答案必须针对 Symfony 4 稍作改动:

使用ConstraintValidatorTestCase 而不是AbstractConstraintValidatorTest。

【讨论】:

    【解决方案6】:

    答案(b):在单元测试中创建验证器(Symfony 2.0)

    如果您构建了 Constraint 和 ConstraintValidator,则根本不需要任何 DI 容器。

    假设你想测试 Symfony 的 Type 约束,它是 TypeValidator。您可以简单地执行以下操作:

    use Symfony\Component\Validator\Constraints\TypeValidator;
    use Symfony\Component\Validator\Constraints\Type;
    
    class TypeValidatorTest extends \PHPUnit_Framework_TestCase
    {
      function testIsValid()
      {
        // The Validator class.
        $v = new TypeValidator();
    
        // Call the isValid() method directly and pass a 
        // configured Type Constraint object (options
        // are passed in an associative array).
    
        $this->assertTrue($v->isValid(5, new Type(array('type' => 'integer'))));
        $this->assertFalse($v->isValid(5, new Type(array('type' => 'string'))));
      }
    }
    

    有了这个,您可以使用任何约束配置检查您喜欢的每个验证器。你既不需要 ValidatorFactory 也不需要 Symfony 内核。

    更新:正如@psylosss 所指出的,这在 Symfony 2.5 中不起作用。它在 Symfony >= 2.1 中也不起作用。 ConstraintValidator 的接口已更改:isValid 已重命名为 validate 并且不再返回布尔值。现在你需要一个ExecutionContextInterface 来初始化一个ConstraintValidator,它本身至少需要一个GlobalExecutionContextInterface 和一个TranslatorInterface...所以基本上没有太多工作就不可能了。

    【讨论】:

    • 它在 symfony 2.5 中产生 Call to undefined method Symfony\Component\Validator\Constraints\TypeValidator::isValid()
    • @psylosss 你是对的。显然 ConstraintValidator 接口已从 Symfony 2.0(此答案的来源)更改为 Symfony 2.1。见GitHub commit。
    【解决方案7】:

    我认为 WebTestCase 没有问题。如果您不想要一个客户端,请不要创建一个;)但是使用可能与您的实际应用程序将使用的服务不同的服务,这是一个潜在的陷阱。所以就个人而言,我是这样做的:

    class ProductServiceTest extends Symfony\Bundle\FrameworkBundle\Test\WebTestCase
    {
    
        /**
         * Setup the kernel.
         *
         * @return null
         */
        public function setUp()
        {
            $kernel = self::getKernelClass();
    
            self::$kernel = new $kernel('dev', true);
            self::$kernel->boot();
        }
    
        public function testFoo(){
            $em = self::$kernel->getContainer()->get('doctrine.orm.entity_manager');
            $v  = self::$kernel->getContainer()->get('validator');
    
            // ...
        }
    
    }
    

    它比 Matt 的答案更干——因为您将重复代码(对于每个测试类)并经常启动内核(对于每个测试方法),但它是独立的,不需要额外的依赖项,所以它取决于根据您的需要。另外,我摆脱了静态需求。

    此外,当您在要测试的环境中启动内核时,您一定会拥有与您的应用程序使用的服务相同的服务——不是默认的或模拟的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多