PHP项目Symfony form与测试工

wen PHP项目 1

本文目录导读:

PHP项目Symfony form与测试工

  1. Symfony Form 组件基础
  2. Form 测试方法
  3. 测试最佳实践

我来为您详细介绍 PHP Symfony 框架中的 Form 组件及其测试方法。

Symfony Form 组件基础

创建 Form 类型

<?php
// src/Form/UserType.php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Email;
class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('username', TextType::class, [
                'label' => '用户名',
                'constraints' => [
                    new NotBlank(['message' => '用户名不能为空']),
                ],
            ])
            ->add('email', EmailType::class, [
                'label' => '邮箱',
                'constraints' => [
                    new NotBlank(),
                    new Email(['message' => '请输入有效的邮箱地址']),
                ],
            ])
            ->add('password', PasswordType::class, [
                'label' => '密码',
                'constraints' => [
                    new NotBlank(),
                ],
            ])
            ->add('roles', ChoiceType::class, [
                'label' => '角色',
                'choices' => [
                    '管理员' => 'ROLE_ADMIN',
                    '用户' => 'ROLE_USER',
                ],
                'multiple' => true,
                'expanded' => true,
            ]);
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => User::class,
            'csrf_protection' => true,
        ]);
    }
}

在 Controller 中使用

<?php
// src/Controller/UserController.php
namespace App\Controller;
use App\Entity\User;
use App\Form\UserType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class UserController extends AbstractController
{
    #[Route('/user/new', name: 'user_new')]
    public function new(Request $request, EntityManagerInterface $entityManager): Response
    {
        $user = new User();
        $form = $this->createForm(UserType::class, $user);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            // 处理表单提交
            $entityManager->persist($user);
            $entityManager->flush();
            $this->addFlash('success', '用户创建成功!');
            return $this->redirectToRoute('user_list');
        }
        return $this->render('user/new.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

Form 测试方法

单元测试 Form Type

<?php
// tests/Form/UserTypeTest.php
namespace App\Tests\Form;
use App\Entity\User;
use App\Form\UserType;
use Symfony\Component\Form\Test\TypeTestCase;
class UserTypeTest extends TypeTestCase
{
    public function testSubmitValidData(): void
    {
        $formData = [
            'username' => 'testuser',
            'email' => 'test@example.com',
            'password' => 'password123',
            'roles' => ['ROLE_USER'],
        ];
        $model = new User();
        $form = $this->factory->create(UserType::class, $model);
        $expected = new User();
        $expected->setUsername('testuser');
        $expected->setEmail('test@example.com');
        $expected->setPassword('password123');
        $expected->setRoles(['ROLE_USER']);
        // 提交数据
        $form->submit($formData);
        // 验证表单是否提交成功
        $this->assertTrue($form->isSynchronized());
        $this->assertTrue($form->isSubmitted());
        $this->assertTrue($form->isValid());
        // 验证 model 数据
        $this->assertEquals($expected->getUsername(), $model->getUsername());
        $this->assertEquals($expected->getEmail(), $model->getEmail());
        $this->assertEquals($expected->getRoles(), $model->getRoles());
    }
    public function testSubmitInvalidData(): void
    {
        $formData = [
            'username' => '',
            'email' => 'invalid-email',
            'password' => '',
            'roles' => [],
        ];
        $form = $this->factory->create(UserType::class, new User());
        $form->submit($formData);
        $this->assertTrue($form->isSubmitted());
        $this->assertFalse($form->isValid());
        // 验证错误信息
        $errors = $form->getErrors(true);
        $this->assertCount(3, $errors); // username, email, password 都有错误
    }
}

测试表单扩展

<?php
// tests/Form/Extension/DateTimeExtensionTest.php
namespace App\Tests\Form\Extension;
use App\Form\Extension\DateTimeExtension;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Test\FormIntegrationTestCase;
class DateTimeExtensionTest extends FormIntegrationTestCase
{
    protected function getTypeExtensions(): array
    {
        return [
            new DateTimeExtension(),
        ];
    }
    public function testDateTimeExtension(): void
    {
        $form = $this->factory->create(DateTimeType::class, null, [
            'widget' => 'single_text',
        ]);
        // 验证扩展是否添加了额外的选项
        $config = $form->getConfig();
        $this->assertTrue($config->hasOption('html5'));
        $this->assertTrue($config->getOption('html5'));
    }
}

功能测试 Controller 中的表单使用

<?php
// tests/Controller/UserControllerTest.php
namespace App\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class UserControllerTest extends WebTestCase
{
    private $client;
    protected function setUp(): void
    {
        $this->client = static::createClient();
    }
    public function testNewUserForm(): void
    {
        $crawler = $this->client->request('GET', '/user/new');
        // 验证表单存在
        $this->assertResponseIsSuccessful();
        $this->assertSelectorExists('form');
        // 提交表单
        $form = $crawler->selectButton('提交')->form([
            'user[username]' => 'testuser',
            'user[email]' => 'test@example.com',
            'user[password]' => 'password123',
            'user[roles]' => ['ROLE_USER'],
        ]);
        $this->client->submit($form);
        // 验证重定向
        $this->assertResponseRedirects('/user/list');
        // 跟踪重定向
        $crawler = $this->client->followRedirect();
        $this->assertSelectorExists('.alert-success');
    }
    public function testNewUserFormValidation(): void
    {
        $crawler = $this->client->request('GET', '/user/new');
        // 提交无效数据
        $form = $crawler->selectButton('提交')->form([
            'user[username]' => '',
            'user[email]' => 'invalid-email',
            'user[password]' => '',
        ]);
        $this->client->submit($form);
        // 验证错误信息
        $this->assertResponseIsSuccessful();
        $this->assertSelectorExists('.form-error');
    }
}

测试自定义 Form 验证器

<?php
// tests/Validator/Constraints/UniqueUserValidatorTest.php
namespace App\Tests\Validator\Constraints;
use App\Entity\User;
use App\Validator\Constraints\UniqueUser;
use App\Validator\Constraints\UniqueUserValidator;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
class UniqueUserValidatorTest extends TestCase
{
    public function testValidateWithExistingUser(): void
    {
        // Mock EntityManager
        $entityManager = $this->createMock(EntityManagerInterface::class);
        $repository = $this->createMock(EntityRepository::class);
        // 模拟数据库中存在同名用户
        $existingUser = new User();
        $existingUser->setUsername('existinguser');
        $repository->method('findOneBy')
            ->willReturn($existingUser);
        $entityManager->method('getRepository')
            ->willReturn($repository);
        // Mock 验证上下文
        $context = $this->createMock(ExecutionContextInterface::class);
        $violation = $this->createMock(ConstraintViolationBuilderInterface::class);
        $context->expects($this->once())
            ->method('buildViolation')
            ->willReturn($violation);
        $violation->expects($this->once())
            ->method('addViolation');
        $validator = new UniqueUserValidator($entityManager);
        $validator->initialize($context);
        $validator->validate('existinguser', new UniqueUser());
    }
    public function testValidateWithNewUser(): void
    {
        // Mock EntityManager,模拟数据库中不存在该用户
        $entityManager = $this->createMock(EntityManagerInterface::class);
        $repository = $this->createMock(EntityRepository::class);
        $repository->method('findOneBy')
            ->willReturn(null);
        $entityManager->method('getRepository')
            ->willReturn($repository);
        $context = $this->createMock(ExecutionContextInterface::class);
        $context->expects($this->never())
            ->method('buildViolation');
        $validator = new UniqueUserValidator($entityManager);
        $validator->initialize($context);
        $validator->validate('newuser', new UniqueUser());
    }
}

测试表单事件订阅

<?php
// tests/Form/EventListener/PasswordEncoderListenerTest.php
namespace App\Tests\Form\EventListener;
use App\Form\EventListener\PasswordEncoderListener;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class PasswordEncoderListenerTest extends TestCase
{
    public function testPasswordEncodingOnSubmit(): void
    {
        $passwordEncoder = $this->createMock(UserPasswordEncoderInterface::class);
        $passwordEncoder->expects($this->once())
            ->method('encodePassword')
            ->willReturn('encoded_password');
        $listener = new PasswordEncoderListener($passwordEncoder);
        // 模拟表单提交事件
        $user = new User();
        $user->setPassword('plain_password');
        $event = $this->createMock(FormEvent::class);
        $event->method('getData')
            ->willReturn($user);
        // 执行监听器
        $listener->onPostSubmit($event);
        // 验证密码已被编码
        $this->assertEquals('encoded_password', $user->getPassword());
    }
    public function testListenerSubscribedEvents(): void
    {
        $events = PasswordEncoderListener::getSubscribedEvents();
        $this->assertArrayHasKey(FormEvents::POST_SUBMIT, $events);
        $this->assertEquals('onPostSubmit', $events[FormEvents::POST_SUBMIT]);
    }
}

测试最佳实践

编写可测试的 Form 配置

<?php
// src/Form/ConfigurableFormType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ConfigurableFormType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'validation_groups' => function (FormInterface $form) {
                $data = $form->getData();
                if ($data->isSpecialUser()) {
                    return ['Default', 'special'];
                }
                return ['Default'];
            },
        ]);
    }
}

测试辅助方法

<?php
// tests/Form/FormTestHelper.php
namespace App\Tests\Form;
trait FormTestHelper
{
    protected function assertFormHasField($form, string $fieldName, string $type = null): void
    {
        $this->assertTrue($form->has($fieldName));
        if ($type) {
            $config = $form->get($fieldName)->getConfig();
            $this->assertInstanceOf($type, $config->getType()->getInnerType());
        }
    }
    protected function assertFormOptionValue($form, string $fieldName, string $option, $expectedValue): void
    {
        $config = $form->get($fieldName)->getConfig();
        $this->assertEquals($expectedValue, $config->getOption($option));
    }
}

使用 Data Provider 进行参数化测试

<?php
// tests/Form/UserTypeDataProviderTest.php
namespace App\Tests\Form;
use App\Entity\User;
use App\Form\UserType;
use Symfony\Component\Form\Test\TypeTestCase;
class UserTypeDataProviderTest extends TypeTestCase
{
    /**
     * @dataProvider validUserDataProvider
     */
    public function testValidUserData(array $formData): void
    {
        $form = $this->factory->create(UserType::class, new User());
        $form->submit($formData);
        $this->assertTrue($form->isSynchronized());
        $this->assertTrue($form->isValid());
    }
    public function validUserDataProvider(): array
    {
        return [
            'standard_user' => [
                [
                    'username' => 'user1',
                    'email' => 'user1@example.com',
                    'password' => 'pass123',
                    'roles' => ['ROLE_USER'],
                ]
            ],
            'admin_user' => [
                [
                    'username' => 'admin',
                    'email' => 'admin@example.com',
                    'password' => 'admin123',
                    'roles' => ['ROLE_ADMIN'],
                ]
            ],
            'user_with_multiple_roles' => [
                [
                    'username' => 'superuser',
                    'email' => 'super@example.com',
                    'password' => 'super123',
                    'roles' => ['ROLE_ADMIN', 'ROLE_USER'],
                ]
            ],
        ];
    }
}

这些测试方法涵盖了 Symfony Form 组件的主要测试场景,可以帮助确保你的表单功能正常工作,记得根据实际项目需求调整测试代码。

抱歉,评论功能暂时关闭!