Symfony2:testing实体validation约束
有没有人有一个很好的方式来unit testing在Symfony2实体的validation约束?
理想情况下,我想要在unit testing中访问dependency injection容器,然后让我访问validation器服务。 一旦我有validation器服务,我可以手动运行它:
$errors = $validator->validate($entity);
我可以扩展WebTestCase
,然后根据文档创build一个client
来访问容器,但是这样做感觉不对。 WebTestCase
和client
在文档中读取更多的工具来testing整个行为 ,因此使用它来unit testing一个实体感觉很WebTestCase
。
那么,有没有人知道如何a)获取容器或b)在unit testing中创buildvalidation器?
好,因为这得到了两票,我猜其他人有兴趣。
我决定把我的铲子拿出来,并且惊喜地(到目前为止),这一点都不难完成。
我记得每个Symfony2组件都可以在独立模式下使用,因此我可以自己创buildvalidation器。
查看文档: https : //github.com/symfony/Validator/blob/master/ValidatorFactory.php
我意识到,既然有一个ValidatorFactory,创build一个validation器是微不足道的(特别是对于我所做的注释所做的validation,尽pipe如果你看看我链接的页面上的docblock,你也可以findvalidationxml和yml的方法)。
第一:
use Symfony\Component\Validator\ValidatorFactory;
接着:
$validator = ValidatorFactory::buildDefault()->getValidator(); $errors = $validator->validate($entity); $this->assertEquals(0, count($errors));
我希望这可以帮助良心的任何人不会让他们使用WebTestCase;)。
我们最终将自己的基础testing用例从一个testing用例中访问依赖容器。 在这里,有问题的class级:
<?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); } }
有了这个基类,你现在可以在你的testing方法中访问validation器服务:
$validator = $this->get('validator');
我们决定使用静态函数而不是类构造函数,但是可以很容易地改变行为来直接将内核实例化到构造函数中,而不是依靠PHPUnit提供的静态方法setUpBeforeClass
。
另外,请记住,您testing用例中的每个单独的testing方法都不会被隔离,因为整个testing用例共享容器。 对容器进行修改可能会对其他testing方法产生影响,但是如果只访问validator
服务,情况就不会如此。 但是,这样,testing用例运行得更快,因为您不需要为每个testing方法实例化和引导新的内核。
为了参考,我们从这个博客文章中find了这个class的灵感。 这是用法语写的,但我更喜欢把它归功于谁:)
问候,
马特
我喜欢Kasheens的回答,但它不再适用于Symfony 2.3。 几乎没有变化:
use Symfony\Component\Validator\Validation;
和
$validator = Validation::createValidatorBuilder()->getValidator();
例如,如果要validation注释,请使用enableAnnotationMapping(),如下所示:
$validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();
其余的保持不变:
$errors = $validator->validate($entity); $this->assertEquals(0, count($errors));
在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类的一个很好的例子
答案(b):在unit testing中创buildvalidation器(Symfony 2.0)
如果你build立了一个Constraint
和ConstraintValidator
你根本不需要任何DI容器。
比方说,你想testingSymfony的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')))); } }
有了这个,你可以用任何约束configuration来检查你喜欢的每个validation器。 你既不需要ValidatorFactory
也不需要Symfony内核。
更新:正如@psylosss指出的,这在Symfony 2.5中不起作用。 在Symfony> = 2.1中也不起作用。
ConstraintValidator
的接口得到了改变:isValid
被重命名为validate
并且不再返回布尔值。 现在你需要一个ExecutionContextInterface
来初始化一个ConstraintValidator
,它本身至less需要一个GlobalExecutionContextInterface
和一个TranslatorInterface
…所以基本上不可能没有太多的工作。
我没有看到WebTestCase的问题。 如果你不想要一个客户端,不要创build一个;)但是使用一个可能不同于你的实际应用程序的服务将会使用,这是一个潜在的陷阱。 所以我亲自做了这样的事情:
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的答案要less – 因为你会重复代码(对于每个testing类)并经常启动内核(对于每个testing方法),但是它是独立的,不需要额外的依赖关系,因此取决于你的需求。 另外我摆脱了静态要求。
而且,当你在你想testing的环境中引导内核的时候,你肯定会有和你的应用程序一样的服务 – 而不是默认的或模拟的。