如何对表单进行单元测试
警告
本文档适用于创建自定义表单类型的开发者。如果您正在使用内置的 Symfony 表单类型或第三方 bundle 提供的表单类型,则无需对它们进行单元测试。
Form 组件由 3 个核心对象组成:表单类型(实现 FormTypeInterface),Form 和 FormView。
程序员通常只操作表单类型类,它充当表单蓝图。它用于生成 Form
和 FormView
。您可以直接测试它,通过模拟它与工厂的交互,但这会很复杂。最好像在真实应用程序中那样将其传递给 FormFactory。这样更容易引导,并且您可以充分信任 Symfony 组件,将它们用作测试基础。
已经有一个类可以帮助您进行测试:TypeTestCase。它用于测试核心类型,您也可以使用它来测试您的类型。
注意
根据您安装 Symfony 或 Symfony Form 组件的方式,测试可能未被下载。如果出现这种情况,请使用 Composer 的 --prefer-source
选项。
基础知识
最简单的 TypeTestCase
实现如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
// tests/Form/Type/TestedTypeTest.php
namespace App\Tests\Form\Type;
use App\Form\Type\TestedType;
use App\Model\TestObject;
use Symfony\Component\Form\Test\TypeTestCase;
class TestedTypeTest extends TypeTestCase
{
public function testSubmitValidData(): void
{
$formData = [
'test' => 'test',
'test2' => 'test2',
];
$model = new TestObject();
// $model will retrieve data from the form submission; pass it as the second argument
$form = $this->factory->create(TestedType::class, $model);
$expected = new TestObject();
// ...populate $expected properties with the data stored in $formData
// submit the data to the form directly
$form->submit($formData);
// This check ensures there are no transformation failures
$this->assertTrue($form->isSynchronized());
// check that $model was modified as expected when the form was submitted
$this->assertEquals($expected, $model);
}
public function testCustomFormView(): void
{
$formData = new TestObject();
// ... prepare the data as you need
// The initial data may be used to compute custom view variables
$view = $this->factory->create(TestedType::class, $formData)
->createView();
$this->assertArrayHasKey('custom_var', $view->vars);
$this->assertSame('expected value', $view->vars['custom_var']);
}
}
那么,它测试什么呢?以下是详细解释。
首先,您验证 FormType
是否可以编译。这包括基本的类继承、buildForm()
方法和选项解析。这应该是您编写的第一个测试
1
$form = $this->factory->create(TestedType::class, $formData);
此测试检查表单使用的任何数据转换器是否产生错误。isSynchronized() 方法仅在数据转换器抛出异常时才设置为 false
1 2
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
注意
不要测试验证:它由一个监听器应用,该监听器在测试用例中不活跃,并且它依赖于验证配置。相反,直接单元测试您的自定义约束,或者阅读本页最后一节中关于如何添加自定义扩展的内容。
接下来,验证表单的提交和映射。以下测试检查是否正确指定了所有字段
1
$this->assertEquals($expected, $formData);
最后,检查 FormView
的创建。您可以检查自定义变量是否存在,并且在您的表单主题中是否可用
1 2
$this->assertArrayHasKey('custom_var', $view->vars);
$this->assertSame('expected value', $view->vars['custom_var']);
提示
使用 PHPUnit 数据提供器,使用相同的测试代码测试多个表单条件。
警告
当您的类型依赖于 EntityType
时,您应该注册 DoctrineOrmExtension,这将需要模拟 ManagerRegistry
。
但是,如果您无法使用模拟来编写测试,则应扩展 KernelTestCase
并使用 form.factory
服务来创建表单。
测试注册为服务的类型
您的表单可以用作服务,因为它依赖于其他服务(例如 Doctrine 实体管理器)。在这些情况下,使用上面的代码将不起作用,因为 Form 组件实例化表单类型时,不会将任何参数传递给构造函数。
为了解决这个问题,您必须模拟注入的依赖项,实例化您自己的表单类型,并使用 PreloadedExtension 以确保 FormRegistry
使用创建的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
// tests/Form/Type/TestedTypeTest.php
namespace App\Tests\Form\Type;
use App\Form\Type\TestedType;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
// ...
class TestedTypeTest extends TypeTestCase
{
private MockObject&EntityManager $entityManager;
protected function setUp(): void
{
// mock any dependencies
$this->entityManager = $this->createMock(EntityManager::class);
parent::setUp();
}
protected function getExtensions(): array
{
// create a type instance with the mocked dependencies
$type = new TestedType($this->entityManager);
return [
// register the type instances with the PreloadedExtension
new PreloadedExtension([$type], []),
];
}
public function testSubmitValidData(): void
{
// ...
// Instead of creating a new instance, the one created in
// getExtensions() will be used.
$form = $this->factory->create(TestedType::class, $formData);
// ... your test
}
}
添加自定义扩展
通常情况下,您会使用一些由表单扩展添加的选项。其中一种情况可能是带有 invalid_message
选项的 ValidatorExtension
。TypeTestCase
仅加载核心表单扩展,这意味着如果您尝试测试依赖于其他扩展的类,则会引发 InvalidOptionsException。getExtensions() 方法允许您返回要注册的扩展列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// tests/Form/Type/TestedTypeTest.php
namespace App\Tests\Form\Type;
// ...
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Validator\Validation;
class TestedTypeTest extends TypeTestCase
{
protected function getExtensions(): array
{
$validator = Validation::createValidator();
// or if you also need to read constraints from attributes
$validator = Validation::createValidatorBuilder()
->enableAttributeMapping()
->getValidator();
return [
new ValidatorExtension($validator),
];
}
// ... your tests
}
注意
默认情况下,只有 CoreExtension 在测试中注册。您可以在 Symfony\Component\Form\Extension
命名空间中找到 Form 组件的其他扩展。
也可以使用 getTypes(),getTypeExtensions() 和 getTypeGuessers() 方法加载自定义表单类型、表单类型扩展或类型猜测器。
在测试表单主题时,请考虑让您的测试扩展 FormLayoutTestCase 类。这通过为您实现 FormIntegrationTestCase 方法,节省了大量样板代码并减少了代码重复。您需要做的就是实现 getTemplatePaths(),getTwigExtensions() 和 getThemes() 方法。