Skip to content

Commit 91171ac

Browse files
committed
Merge branch 'cookbook/form/unit-testing' of github.com:marekkalnik/symfony-docs into marekkalnik-cookbook/form/unit-testing
2 parents 124901d + 5e97d69 commit 91171ac

File tree

1 file changed

+249
-0
lines changed

1 file changed

+249
-0
lines changed

cookbook/form/unit_testing.rst

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
.. index::
2+
single: Form; Form testing
3+
4+
Testing forms
5+
=============
6+
7+
The Form Component consists of 3 core objects: a FormType (implementing
8+
:class:`Symfony\\Component\\Form\\FormTypeInterface`), the
9+
:class:`Symfony\\Component\\Form\\Form` and the
10+
:class:`Symfony\\Component\\Form\\FormView`.
11+
12+
The only class that is usually manipulated by programmers is the FormType class
13+
which serves as a form blueprint. It is used to generate the Form and the
14+
FormView. You could test it directly by mocking its interactions with the
15+
factory but it would be complex. It is better to pass it to FormFactory like it
16+
is done in a real application. It is simple to bootstrap and we trust Symfony
17+
components enough to use them as a testing base.
18+
19+
There is already a class that you can benefit from for simple FormTypes
20+
testing, the
21+
:class:`Symfony\\Component\\Form\\Tests\\Extension\\Core\\Type\\TypeTestCase`.
22+
It is used to test the core types and you can use it to test yours too.
23+
24+
.. note::
25+
26+
Depending on the way you installed your Symfony or Symfony Form Component
27+
the tests may not be downloaded. Use the --prefer-source option with
28+
composer if this is the case.
29+
30+
The Basics
31+
----------
32+
33+
The simplest TypeTestCase implementation looks like the following::
34+
35+
// src/Acme/TestBundle/Tests/Form/Type/TestedTypeTests.php
36+
namespace Acme\TestBundle\Tests\Form\Type;
37+
38+
use Acme\TestBundle\Form\Type\TestedType;
39+
use Acme\TestBundle\Model\TestObject;
40+
use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase;
41+
42+
class TestedTypeTest extends TypeTestCase
43+
{
44+
public function testBindValidData()
45+
{
46+
$formData = array(
47+
'test' => 'test',
48+
'test2' => 'test2',
49+
);
50+
51+
$type = new TestedType();
52+
$form = $this->factory->create($type);
53+
54+
$object = new TestObject();
55+
$object->fromArray($formData);
56+
57+
$form->bind($formData);
58+
59+
$this->assertTrue($form->isSynchronized());
60+
$this->assertEquals($object, $form->getData());
61+
62+
$view = $form->createView();
63+
$children = $view->children;
64+
65+
foreach (array_keys($formData) as $key) {
66+
$this->assertArrayHasKey($key, $children);
67+
}
68+
}
69+
}
70+
71+
So, what does it test? Let's explain it line by line.
72+
73+
First we verify if the FormType compiles. This includes basic class
74+
inheritance, the buildForm function and options resolution. This should
75+
be the first test you write::
76+
77+
$type = new TestedType();
78+
$form = $this->factory->create($type);
79+
80+
81+
This test checks if none of your DataTransformers used by the form
82+
failed. The isSynchronized is only set to false if a DataTransformer
83+
throws an exception::
84+
85+
$form->bind($formData);
86+
$this->assertTrue($form->isSynchronized());
87+
88+
.. note::
89+
90+
We don't check the validation – it is done by a listener that is not
91+
active in the test case and it relies on validation configuration.
92+
You would need to bootstrap the whole kernel to do it. Write
93+
separate tests to check your validators.
94+
95+
Next we verify the binding and mapping of the form. The test below
96+
checks if all the fields are correctly specified::
97+
98+
$this->assertEquals($object, $form->getData());
99+
100+
At last we check the creation of the FormView. You should check if all
101+
widgets you want to display are available in the children property::
102+
103+
$view = $form->createView();
104+
$children = $view->children;
105+
106+
foreach (array_keys($formData) as $key) {
107+
$this->assertArrayHasKey($key, $children);
108+
}
109+
110+
Adding a Type your form depends on
111+
----------------------------------
112+
113+
Your form may depend on other types that are defined as services. It
114+
would be defined like this::
115+
116+
// src/Acme/TestBundle/Form/Type/TestedType.php
117+
118+
// ... the buildForm method
119+
$builder->add('acme_test_child_type');
120+
121+
To create your form correctly you need to make the type available to the
122+
form factory in your test. The easiest way is to register it manually
123+
before creating the parent form::
124+
125+
// src/Acme/TestBundle/Tests/Form/Type/TestedTypeTests.php
126+
namespace Acme\TestBundle\Tests\Form\Type;
127+
128+
use Acme\TestBundle\Form\Type\TestedType;
129+
use Acme\TestBundle\Model\TestObject;
130+
use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase;
131+
132+
class TestedTypeTest extends TypeTestCase
133+
{
134+
public function testBindValidData()
135+
{
136+
$this->factory->addType(new TestChildType());
137+
138+
$type = new TestedType();
139+
$form = $this->factory->create($type);
140+
141+
// ... your test
142+
}
143+
}
144+
145+
.. caution::
146+
147+
Make sure the child type you add is well tested. Otherwise you may
148+
be getting errors that are not related to the form you are currently
149+
testing but to its children.
150+
151+
Adding custom extensions
152+
------------------------
153+
154+
It often happens that you use some options that are added by form
155+
extensions. One of the cases may be the ValidatorExtension with its
156+
invalid_message option. The TypeTestCase loads only the core Form
157+
Extension so an “Invalid option” exception will be raised if you try to
158+
use it for testing a class that depends on other extensions. You need
159+
add the dependencies to the Factory object::
160+
161+
// src/Acme/TestBundle/Tests/Form/Type/TestedTypeTests.php
162+
namespace Acme\TestBundle\Tests\Form\Type;
163+
164+
use Acme\TestBundle\Form\Type\TestedType;
165+
use Acme\TestBundle\Model\TestObject;
166+
use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase;
167+
168+
class TestedTypeTest extends TypeTestCase
169+
{
170+
protected function setUp()
171+
{
172+
parent::setUp();
173+
174+
$this->factory = Forms::createFormFactoryBuilder()
175+
->addTypeExtension(
176+
new FormTypeValidatorExtension(
177+
$this->getMock('Symfony\Component\Validator\ValidatorInterface')
178+
)
179+
)
180+
->addTypeGuesser(
181+
$this->getMockBuilder(
182+
'Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser'
183+
)
184+
->disableOriginalConstructor()
185+
->getMock()
186+
)
187+
->getFormFactory();
188+
189+
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
190+
$this->builder = new FormBuilder(null, null, $this->dispatcher, $this->factory);
191+
}
192+
193+
// ... your tests
194+
}
195+
196+
Testing against different sets of data
197+
--------------------------------------
198+
199+
If you are not familiar yet with PHPUnit's `data providers`_ it would be
200+
a good opportunity to use them::
201+
202+
// src/Acme/TestBundle/Tests/Form/Type/TestedTypeTests.php
203+
namespace Acme\TestBundle\Tests\Form\Type;
204+
205+
use Acme\TestBundle\Form\Type\TestedType;
206+
use Acme\TestBundle\Model\TestObject;
207+
use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase;
208+
209+
class TestedTypeTest extends TypeTestCase
210+
{
211+
212+
/**
213+
* @dataProvider getValidTestData
214+
*/
215+
public function testForm($data)
216+
{
217+
// ... your test
218+
}
219+
220+
public function getValidTestData()
221+
{
222+
return array(
223+
array(
224+
'data' => array(
225+
'test' => 'test',
226+
'test2' => 'test2',
227+
),
228+
),
229+
array(
230+
'data' => array(),
231+
),
232+
array(
233+
'data' => array(
234+
'test' => null,
235+
'test2' => null,
236+
),
237+
),
238+
);
239+
}
240+
}
241+
242+
The code above will run your test three times with 3 different sets of
243+
data. This allows for decoupling the test fixtures from the tests and
244+
easily testing against multiple sets of data.
245+
246+
You can also pass another argument, such as a boolean if the form has to
247+
be synchronized with the given set of data or not etc.
248+
249+
.. _`data providers`: http://www.phpunit.de/manual/3.7/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers

0 commit comments

Comments
 (0)