如何使用 EntityType 字段对 Symfony 4 表单进行单元测试
Posted
技术标签:
【中文标题】如何使用 EntityType 字段对 Symfony 4 表单进行单元测试【英文标题】:How to unit test Symfony 4 Form with EntityType field 【发布时间】:2018-09-27 05:07:38 【问题描述】:当我运行测试时:
$ ./vendor/bin/simple-phpunit tests/Unit/Form/ProductFormTest.php
这是我终端的输出:
Sebastian Bergmann 的 PHPUnit 6.5.8
和贡献者。
运行时:PHP 7.2.4-1+ubuntu16.04.1+deb.sury.org+1 和 Xdebug 2.7.0alpha2-dev 配置:/var/www/project/phpunit.xml.dist
测试应用\Tests\Unit\Form\ProductFormTest E 1 / 1 (100%)
时间:551 毫秒,内存:6.00MB
有 1 个错误:
1) App\Tests\Unit\Form\ProductFormTest::formSubmitsValidData Symfony\Component\Form\Exception\RuntimeException: 类 “App\Entity\Supplier”似乎不是一个托管的 Doctrine 实体。做过 你忘了映射吗?
/var/www/project/vendor/symfony/doctrine-bridge/Form/Type/DoctrineType.php:205 /var/www/project/vendor/symfony/options-resolver/OptionsResolver.php:858 /var/www/project/vendor/symfony/doctrine-bridge/Form/Type/DoctrineType.php:130 /var/www/project/vendor/symfony/options-resolver/OptionsResolver.php:766 /var/www/project/vendor/symfony/options-resolver/OptionsResolver.php:698 /var/www/project/vendor/symfony/form/ResolvedFormType.php:95 /var/www/project/vendor/symfony/form/FormFactory.php:76 /var/www/project/vendor/symfony/form/FormBuilder.php:97 /var/www/project/vendor/symfony/form/FormBuilder.php:256 /var/www/project/vendor/symfony/form/FormBuilder.php:206 /var/www/project/vendor/symfony/form/FormFactory.php:30 /var/www/project/tests/Unit/Form/ProductFormTest.php:86
错误!测试:1,断言:0,错误:1。
这个错误是在模拟 ManagerRegistry 类之后开始的。似乎在这个单元测试中没有针对存在的学说实体的映射。
有没有一种简洁的方法来测试带有“Symfony\Bridge\Doctrine\Form\Type\EntityType”字段的表单?
src\App\Entity\Product.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Entity\Supplier;
/**
* Product Entity
*
* @ORM\Entity(repositoryClass = "App\Repository\ProductRepository")
* @ORM\Table(name = "product")
*/
class Product
/**
* Constructor
*/
public function __construct()
parent::__construct();
$this->setType(AbstractProduct::TYPE_PARENT);
/**
* To String
*
* @return string
*/
public function __toString()
return "[" . $this->id . "] Product: " . $this->ean . " | " . $this->name;
/**
* ID
*
* @var integer
*
* @ORM\Id
* @ORM\Column(name = "product_id", type = "integer")
* @ORM\GeneratedValue(strategy = "AUTO")
*/
protected $id;
/**
* EAN (European Article Number)
*
* @var string
*
* @ORM\Column(name = "product_ean", type = "string", length = 13)
*/
protected $ean;
/**
* Name
*
* @var string
*
* @ORM\Column(name = "product_name", type = "string", length = 128)
*/
protected $name;
/**
* Description
*
* @var string
*
* @ORM\Column(name = "product_description", type = "text", nullable = true)
*/
protected $description;
/**
* Supplier
*
* Many Products have one Supplier
*
* @var Supplier
*
* @ORM\ManyToOne(targetEntity = "Supplier", inversedBy = "products")
* @ORM\JoinColumn(name = "supplier_id", referencedColumnName = "supplier_id")
*/
protected $supplier;
/**
* Get id
*
* @return integer
*/
public function getId()
return $this->id;
/**
* Set ean
*
* @param string $ean
*
* @return AbstractProduct
*/
public function setEan($ean)
$this->ean = $ean;
return $this;
/**
* Get ean
*
* @return string
*/
public function getEan()
return $this->ean;
/**
* Set name
*
* @param string $name
*
* @return AbstractProduct
*/
public function setName($name)
$this->name = $name;
return $this;
/**
* Get name
*
* @return string
*/
public function getName()
return $this->name;
/**
* Set description
*
* @param string $description
*
* @return AbstractProduct
*/
public function setDescription($description)
$this->description = $description;
return $this;
/**
* Get description
*
* @return string
*/
public function getDescription()
return $this->description;
/**
* Set supplier
*
* @param \App\Entity\Supplier $supplier
*
* @return Product
*/
public function setSupplier(Supplier $supplier = null)
$this->supplier = $supplier;
return $this;
/**
* Get supplier
*
* @return \App\Entity\Supplier
*/
public function getSupplier()
return $this->supplier;
src\App\Form\ProductForm.php
namespace App\Form;
use App\Entity\Supplier;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class ProductForm extends AbstractType
/**
* @inheritdoc
*/
public function buildForm(FormBuilderInterface $builder, array $options)
$translationDomain = "product";
/*
* Card
*/
$builder->add("ean", TextType::class, [
"label" => "product.ean",
"required" => true,
"translation_domain" => $translationDomain,
]);
$builder->add("name", TextType::class, [
"label" => "product.name",
"required" => true,
"translation_domain" => $translationDomain,
]);
$builder->add("supplier", EntityType::class, [
"class" => Supplier::class,
"choice_label" => "name",
"label" => "supplier.name",
"required" => false,
"translation_domain" => "supplier",
]);
测试\Unit\Form\ProductFormTest.php
namespace App\Tests\Unit\Form;
use App\Entity\Product;
use App\Form\ProductForm;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Bridge\Doctrine\ManagerRegistry;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
class ProductFormTest extends TypeTestCase
/**
* @var ManagerRegistry
*/
private $_managerRegistry;
/**
* @inheritdoc
*/
protected function setUp()
$this->_managerRegistry = $this->createMock(ManagerRegistry::class);
parent::setUp();
/**
* @inheritdoc
*/
protected function tearDown()
$this->_managerRegistry = null;
parent::tearDown();
/**
* @inheritdoc
*/
protected function getExtensions()
$entityType = new EntityType($this->_managerRegistry);
return [
new PreloadedExtension([$entityType], [])
];
/**
* @test
*/
public function formSubmitsValidData()
$createdAt = new \DateTime();
$formData = [
"ean" => "8718923400440",
"name" => "Plumbus",
"description" => "This is a household device so common it does not need an introduction",
];
$productComparedToForm = new Product();
$productComparedToForm
->setEan($formData["ean"])
->setName($formData["name"])
;
$productHandledByForm = new Product();
$form = $this->factory->create(ProductForm::class, $productHandledByForm);
$form->submit($formData);
static::assertTrue($form->isSynchronized());
static::assertEquals($productComparedToForm, $productHandledByForm);
$view = $form->createView();
foreach (array_keys($formData) as $key)
static::assertArrayHasKey($key, $view->children);
【问题讨论】:
【参考方案1】:首先你的测试用例应该从Symfony\Component\Form\Test\TypeTestCase
扩展。
那么你的测试应该是这样的:
// Example heavily inspired by EntityTypeTest inside the Symfony Bridge
class ProductTypeTest extends TypeTestCase
/**
* @var EntityManager
*/
private $em;
/**
* @var \PHPUnit_Framework_MockObject_MockObject|ManagerRegistry
*/
private $emRegistry;
protected function setUp()
$this->em = DoctrineTestHelper::createTestEntityManager();
$this->emRegistry = $this->createRegistryMock('default', $this->em);
parent::setUp();
$schemaTool = new SchemaTool($this->em);
// This is the important part for you !
$classes = [$this->em->getClassMetadata(Supplier::class)];
try
$schemaTool->dropSchema($classes);
catch (\Exception $e)
try
$schemaTool->createSchema($classes);
catch (\Exception $e)
protected function createRegistryMock($name, $em)
$registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock();
$registry->expects($this->any())
->method('getManager')
->with($this->equalTo($name))
->will($this->returnValue($em));
return $registry;
protected function getExtensions()
return array_merge(parent::getExtensions(), array(
new DoctrineOrmExtension($this->emRegistry),
));
protected function tearDown()
parent::tearDown();
$this->em = null;
$this->emRegistry = null;
【讨论】:
你能告诉我 $this->createRegistryMock() 方法中发生了什么吗? 这会导致另一个错误:ArgumentCountError: Too little arguments to function Doctrine\Common\Persistence\AbstractManagerRegistry::__construct(), 0 pass @Nek : 测试应该扩展TypeTestCase
就像在 OP 和 SF 文档中一样 => symfony.com/doc/current/form/unit_testing.html
你是绝对正确的。我的错。下次请考虑编辑否决票;-)。
这导致“类“App\Entity\Field”似乎不是托管的 Doctrine 实体。您忘记映射了吗?” (Field::class 是 EntityTyp 中使用的实体,如上例中的 Supplier::class)。【参考方案2】:
如果上述解决方案不起作用,则另一种解决方案。另一个应用程序的示例。
namespace App\Tests\Form;
use App\Entity\BusinessDepartment;
use App\Entity\Contact;
use App\Form\ContactForm;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Symfony\Component\Form\Test\TypeTestCase;
class ContactFormTest extends TypeTestCase
protected function getExtensions()
$mockEntityManager = $this->createMock(EntityManager::class);
$mockEntityManager->method('getClassMetadata')
->willReturn(new ClassMetadata(BusinessDepartment::class))
;
$execute = $this->createMock(AbstractQuery::class);
$execute->method('execute')
->willReturn([]);
$query = $this->createMock(QueryBuilder::class);
$query->method('getQuery')
->willReturn($execute);
$entityRepository = $this->createMock(EntityRepository::class);
$entityRepository->method('createQueryBuilder')
->willReturn($query)
;
$mockEntityManager->method('getRepository')->willReturn($entityRepository);
$mockRegistry = $this->createMock(ManagerRegistry::class);
$mockRegistry->method('getManagerForClass')
->willReturn($mockEntityManager)
;
return array_merge(parent::getExtensions(), [new DoctrineOrmExtension($mockRegistry)]);
public function testBuildForm()
$data = [
'name' => 'nameTest',
'firstName' => 'firstnameTest',
'email' => 'test_email@gmail.com',
'message' => 'messageTest'
];
$contact = new Contact();
$form = $this->factory->create( ContactForm::class, $contact);
$contactToCompare = new Contact();
$contactToCompare->setName($data['name']);
$contactToCompare->setFirstName($data['firstName']);
$contactToCompare->setEmail($data['email']);
$contactToCompare->setMessage($data['message']);
//check the submission
$form->submit($data);
$this->assertTrue($form->isSynchronized());
$this->assertEquals($contact->getName(), $contactToCompare->getName());
$this->assertEquals($contact->getFirstName(), $contactToCompare->getFirstName());
$this->assertEquals($contact->getEmail(), $contactToCompare->getEmail());
$this->assertEquals($contact->getMessage(), $contactToCompare->getMessage());
【讨论】:
您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。【参考方案3】:如果上述解决方案不起作用,则另一种解决方案。另一个应用程序的示例。
ContactFromTest.php
namespace App\Tests\Form;
use App\Entity\BusinessDepartment;
use App\Entity\Contact;
use App\Form\ContactForm;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Symfony\Component\Form\Test\TypeTestCase;
class ContactFormTest extends TypeTestCase
protected function getExtensions()
$mockEntityManager = $this->createMock(EntityManager::class);
$mockEntityManager->method('getClassMetadata')
->willReturn(new ClassMetadata(BusinessDepartment::class))
;
$execute = $this->createMock(AbstractQuery::class);
$execute->method('execute')
->willReturn([]);
$query = $this->createMock(QueryBuilder::class);
$query->method('getQuery')
->willReturn($execute);
$entityRepository = $this->createMock(EntityRepository::class);
$entityRepository->method('createQueryBuilder')
->willReturn($query)
;
$mockEntityManager->method('getRepository')->willReturn($entityRepository);
$mockRegistry = $this->createMock(ManagerRegistry::class);
$mockRegistry->method('getManagerForClass')
->willReturn($mockEntityManager)
;
return array_merge(parent::getExtensions(), [new DoctrineOrmExtension($mockRegistry)]);
public function testBuildForm()
$data = [
'name' => 'nameTest',
'firstName' => 'firstnameTest',
'email' => 'test_email@gmail.com',
'message' => 'messageTest'
];
$contact = new Contact();
$form = $this->factory->create( ContactForm::class, $contact);
$contactToCompare = new Contact();
$contactToCompare->setName($data['name']);
$contactToCompare->setFirstName($data['firstName']);
$contactToCompare->setEmail($data['email']);
$contactToCompare->setMessage($data['message']);
//check the submission
$form->submit($data);
$this->assertTrue($form->isSynchronized());
$this->assertEquals($contact->getName(), $contactToCompare->getName());
$this->assertEquals($contact->getFirstName(), $contactToCompare->getFirstName());
$this->assertEquals($contact->getEmail(), $contactToCompare->getEmail());
$this->assertEquals($contact->getMessage(), $contactToCompare->getMessage());
ContacForm.php
class ContactForm extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
$builder
->add('name',TextType::class, [
'label' => 'Nom'
])
->add('firstName',TextType::class, [
'label' => 'Prénom'
])
->add('email', EmailType::class, [
'label' => 'Email'
])
->add('businessDepartment', EntityType::class, [
'label' => 'Département à contacter',
'class' => BusinessDepartment::class,
'choice_value' => 'id',
'choice_label' => 'nameDepartment',
])
->add('message', TextareaType::class, [
'label' => 'Votre message'
])
;
【讨论】:
您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。以上是关于如何使用 EntityType 字段对 Symfony 4 表单进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章
预填充 EntityType 类型的 Symfony 表单的字段
如何防止 EntityType 在与同一实体(父)的多对一关系中显示当前对象?
Symfony sonata EntityType 编辑表单 - 选择 - 获取其他当前