有没有人有很好的方法在Symfony2中对实体的验证约束进行单元测试?
理想情况下,我希望能够在单元测试中访问依赖注入容器,这样我就可以访问验证器服务。一旦我有验证器服务,我就可以手动运行它:
$errors = $validator->validate($entity);
我可以扩展WebTestCase
,然后根据文档创建一个client
来到容器,但感觉不对。 WebTestCase
和client
在文档中读取了更多作为整体测试操作的工具,因此使用它来对单元进行单元测试时会感到不安。
那么,有没有人知道如何a)获取容器或b)在单元测试中创建验证器?
答案 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);
}
}
的好样本
答案 4 :(得分:2)
如果您构建了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&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回答的要少 - 因为你将重复代码(对于每个测试类)并经常引导内核(对于每个测试方法),但它是自包含的并且不需要额外的依赖,所以它取决于根据您的需求。另外,我摆脱了静态要求。
此外,当您在要测试的环境中启动内核时,您肯定会使用与您的应用程序相同的服务 - 而不是默认或模拟。