Як проводити модульне тестування ваших форм

Дата оновлення перекладу 2022-12-14

Як проводити модульне тестування ваших форм

Caution

Ця стаття призначена для розробників, які створюють користувацькі типи форми. Якщо ви використовуєте вбудовані типи форми Symfony або типи форми, надані сторонніми пакетами, вам не потрібно проводити їх модульне тестування.

Компонент Form складається з 3 базових обʼєктів: типу форми (що реалізує FormTypeInterface), Form і FormView.

Єдиний клас, який зазвичай змінюють програмісти - це клас типу форми, який слугує схемою форми. Він використовується для генерування Form та FormView. Ви можете протестувати його напряму, зімітувавши його взаємодії з фабрикою, але це буде складно. Краще передати його у FormFactory так, как як це робиться у справжньому додатку. Його легко використовувати в початковому завантаженні, і ви можете довіряти компонентам Symfony достатньо, щоб використовувати їх в якості бази для тестування.

Вже існує клас, від якого ви можете отримати користь у тестуванні: TypeTestCase. Він використовується для тестування базових типів і ви можете використати його для тестування власних типів.

Note

В залежності від того, як ви встановили вашу Symfony або компонент Form, тести можуть бути не завантажені. У такому випадку, використайте опцію --prefer-source в Composer.

Основи

Найпростіша реалізація 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()
    {
        $formData = [
            'test' => 'test',
            'test2' => 'test2',
        ];

        $model = new TestObject();
        // $model вилучить дані з відправки форми; передати в якості другого аргументу
        $form = $this->factory->create(TestedType::class, $model);

        $expected = new TestObject();
        // ...наповнити властивості $object даними, що зберігаються у $formData

        // відправити дані у форму напряму
        $form->submit($formData);

        // Ця перевірка гарантує, що помилки перетворення відсутні
        $this->assertTrue($form->isSynchronized());

        // перевірити, що $model було змінено, як очікувалося, коли форма була відправлена
        $this->assertEquals($expected, $model);
    }

    public function testCustomFormView()
    {
        $formData = new TestObject();
        // ... підготувати дані, як вам потрібно

        // Початкові дані можуть бути використані для обчислення змінних користувацького перегляду
        $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());

Note

Не тестуйте валідацію: вона застосовується слухачем, який не активний у випадку тестування і покладається на конфігурацію валідації. Замість цього, проведіть модульне тестування ваших користувацьких обмежень напряму, або прочитайте, як додавати користувацькі розширення в останньому розділі цієї сторінки.

Далі, верифікуйте відправку та відображення форми. Тест нижче перевіряє, чи правильно були вказані всі поля:

1
$this->assertEquals($expected, $formData);

Нарешті, перевірте створення FormView. Ви повинні перевірити, чи всі віджети, які ви хочете відобразити, досступні у дочірній властивості:

1
2
$this->assertArrayHasKey('custom_var', $view->vars);
$this->assertSame('expected value', $view->vars['custom_var']);

Tip

Використайте постачальники даних PHPUnit, щоб тестувати умови декількох форм, використовуючи один і той же код тестування.

Caution

Якщо ваш тип покладається на 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\Common\Persistence\ObjectManager;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
// ...

class TestedTypeTest extends TypeTestCase
{
    private $entityManager;

    protected function setUp(): void
    {
        // зімітувати будь-які залежності
        $this->entityManager = $this->createMock(ObjectManager::class);

        parent::setUp();
    }

    protected function getExtensions()
    {
        // створити екземпляр типу з імітованими залежностями
        $type = new TestedType($this->entityManager);

        return array(
            // зареєструвати екземпляри типів у PreloadedExtension
            new PreloadedExtension(array($type), array()),
        );
    }

    public function testSubmitValidData()
    {
        // ...

        // Змість створення новго екземпляру, буде використано створений в
        // getExtensions().
        $form = $this->factory->create(TestedType::class, $formData);

        // ... ваш тест
    }
}

Додавання користувацьких розширень

Часто буває так, що ви використовуєте якісь опції, додані розширеннями форми. Одним з випадків може бути ValidatorExtension з його опцією invalid_message. 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
26
// 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()
    {
        $validator = Validation::createValidator();

        // або, якщо вам також потрібно читати обмеження з анотацій
        $validator = Validation::createValidatorBuilder()
            ->enableAnnotationMapping(true)
            ->addDefaultDoctrineAnnotationReader()
            ->getValidator();

        return [
            new ValidatorExtension($validator),
        ];
    }

    // ... ваші тести
}

Note

За замовчуванням, лише CoreExtension зареєстровані у тестах. Ви можете знайти інші розширення з компонента Form у просторі імен Symfony\Component\Form\Extension.

Також можливо завантажувати користувацькі типи форми, розширення типів форми або вгадувачі типів, використовуючи методи getTypes(), getTypeExtensions() і getTypeGuessers().