跳到内容

如何对表单进行单元测试

编辑此页

警告

本文档适用于创建自定义表单类型的开发者。如果您正在使用内置的 Symfony 表单类型或第三方 bundle 提供的表单类型,则无需对它们进行单元测试。

Form 组件由 3 个核心对象组成:表单类型(实现 FormTypeInterface),FormFormView

程序员通常只操作表单类型类,它充当表单蓝图。它用于生成 FormFormView。您可以直接测试它,通过模拟它与工厂的交互,但这会很复杂。最好像在真实应用程序中那样将其传递给 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 选项的 ValidatorExtensionTypeTestCase 仅加载核心表单扩展,这意味着如果您尝试测试依赖于其他扩展的类,则会引发 InvalidOptionsExceptiongetExtensions() 方法允许您返回要注册的扩展列表

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() 方法。

这项工作,包括代码示例,在 Creative Commons BY-SA 3.0 许可下获得许可。
目录
    版本