FormTypeで独自で定義したバリデーションを使う場合に、TypeTestCaseを使ってテストを実装するこが多いかと思います。
constraints
を設定している場合は Extensions
を使うことでテストすることが出来ます。
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
|
<?php
namespace App\Form\Type;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints as Assert;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) :void
{
$builder
->add('name', TextType::class, [
'label' => '名前',
'required' => true
])
->add('email', TextType::class, [
'label' => 'メールアドレス',
'required' => true,
'constraints' => [
new Assert\Email()
],
]);
$builder->addModelTransformer(
new CallbackTransformer(
static function ($user) {
if ($user instanceof User) {
return [
'name' => $user->getName(),
'email' => $user->getEmail()
];
}
},
static function ($data) {
return new User($data['name'], $data['email']);
}
)
);
}
}
|
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
|
<?php
namespace App\Tests\Form\Type;
use App\Form\Type\UserType;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Validator\Validation;
class UserTypeTest extends TypeTestCase
{
public function testSubmit(): void
{
$formData = [
'name' => 'test',
'email' => '[email protected]'
];
$form = $this->factory->create(UserType::class);
$form->submit($formData);
self::assertTrue($form->isValid());
}
protected function getExtensions(): array
{
$validator = Validation::createValidator();
return [new ValidatorExtension($validator)];
}
}
|
ここでの問題点
UserTypeカスタムなValidatorを利用し、且つValidatorがコンストラクタで引数をもつ下のようなValidatorをFormで利用してた場合は、上のテストコードではエラーになります。
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
|
<?php
namespace App\Validator\Constraints;
use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class DuplicateEmailValidator extends ConstraintValidator
{
/**
* @var UserRepository
*/
private $repository;
/**
* @param UserRepository $repository
*/
public function __construct(UserRepository $repository)
{
$this->repository = $repository;
}
public function validate($value, Constraint $constraint) :void
{
$user = $this->repository->findOneBy(['email' => $value]);
if ($user instanceof User) {
$this->context
->buildViolation($constraint->message)
->addViolation();
}
}
}
|
どう解決するのか?
いくつか解決策があるとは思いますが、今回はgetExtensions()
メソッドでDuplicateEmailValidatorをモックするという方法で対応しました。
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
|
<?php
namespace App\Tests\Form\Type;
use App\Form\Type\UserType;
use App\Validator\Constraints\DuplicateEmailValidator;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\Validation;
class UserTypeTest extends TypeTestCase
{
public function testSubmit(): void
{
$formData = [
'name' => 'test',
'email' => '[email protected]'
];
$form = $this->factory->create(UserType::class);
$form->submit($formData);
self::assertTrue($form->isValid());
}
protected function getExtensions(): array
{
$validatorFactory = new class() extends ConstraintValidatorFactory {
public function setValidator($className, $object): void
{
$this->validators[$className] = $object;
}
};
$duplicateEmailValidator = $this->prophesize(DuplicateEmailValidator::class);
$validatorFactory->setValidator(DuplicateEmailValidator::class, $duplicateEmailValidator->reveal());
$validator = Validation::createValidatorBuilder()
->setConstraintValidatorFactory($validatorFactory)
->getValidator();
return [new ValidatorExtension($validator)];
}
}
|
ConstraintValidatorFactoryを継承してvalidators
プロパティの中にMockObjectを設定出来るようにします。
その後prophecyでモックを作成して、ValidatorFactoryに設定します。
あとはValidation::createValidatorBuilder()
でValidatorBuilderを生成して、Factoryオブジェクトを設定して、Validatorを取得するという流れです。
バリデーションを含めてテストしなくていいのか?
「バリデーションがモックされて正しくテストできない」と考える方もいるかと思います。
本当にFormのテストでバリデーションをテストする必要があるのでしょうか?
FormTypeとしての責務はなんでしょうか?
個人的にはバリデーションは別にテストすればいいと考えています。
どこまでやるかは個人の考え方、チームの方針などによって見定めていくと良いでしょう。
最後に
これば正しい方法ではなくて、今回ボクが使用したテストの方法になります。
もっといい方法があるよというかたはぜひブログ書いてください!
参考