Symfony2:测试实体验证约束

时间:2011-08-10 00:22:34

标签: symfony

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

理想情况下,我希望能够在单元测试中访问依赖注入容器,这样我就可以访问验证器服务。一旦我有验证器服务,我就可以手动运行它:

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

我可以扩展WebTestCase,然后根据文档创建一个client来到容器,但感觉不对。 WebTestCaseclient在文档中读取了更多作为整体测试操作的工具,因此使用它来对单元进行单元测试时会感到不安。

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

7 个答案:

答案 0 :(得分:61)

好的,因为这得到了两票我猜其他人都很感兴趣。

我决定拿出我的铲子并且惊喜(到目前为止),这根本不是很难实现的。

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

查看文档:{​​{3}}

我意识到,由于有一个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的人;)。

答案 1 :(得分:37)

我们最终会推出您自己的基本测试用例,以便从测试用例中访问依赖容器。这是有问题的课程:

<?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中找到了这个课程的灵感。它是用法语写的,但我更愿意赞扬它属于谁:)

的问候,
马特

答案 2 :(得分:27)

我喜欢Kasheens的答案,但它不再适用于Symfony 2.3。 几乎没有变化:

use Symfony\Component\Validator\Validation;

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

如果要验证Annotations,请使用如下所示的enableAnnotationMapping():

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

其余的保持不变:

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

答案 3 :(得分:5)

使用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

的好样本

答案 4 :(得分:2)

答案(b):在单元测试(Symfony 2.0)

中创建验证器

如果您构建了ConstraintConstraintValidator,则根本不需要任何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&gt; = 2.1。来自ConstraintValidator的界面已更改:isValid已重命名为validate,并且不再返回布尔值。现在你需要一个ExecutionContextInterface来初始化一个ConstraintValidator,它本身至少需要一个GlobalExecutionContextInterface和一个TranslatorInterface ......所以基本上不可能没有太多的工作。

答案 5 :(得分:2)

对于https://stackoverflow.com/a/41884661/4560833,答案必须对Symfony 4稍作更改:

使用ConstraintValidatorTestCase代替AbstractConstraintValidatorTest

答案 6 :(得分:0)

我没有看到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回答的要少 - 因为你将重复代码(对于每个测试类)并经常引导内核(对于每个测试方法),但它是自包含的并且不需要额外的依赖,所以它取决于根据您的需求。另外,我摆脱了静态要求。

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