How to achieve test isolation with Symfony forms and data transformers? : <p><strong><em>Note:</strong> This is Symfony &lt; 2.6 but I believe the same overall issue applies regardless of version</em></p>

<p>To start, consider this form type that is designed to represent one-or-more entities as a hidden field (namespace stuff omitted for brevity)</p>

<pre><code>class HiddenEntityType extends AbstractType
{
    /**
     * @var EntityManager
     */
    protected $em;

    public function __construct(EntityManager $em)
    {
        $this-&gt;em = $em;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if ($options['multiple']) {
            $builder-&gt;addViewTransformer(
                new EntitiesToPrimaryKeysTransformer(
                    $this-&gt;em-&gt;getRepository($options['class']),
                    $options['get_pk_callback'],
                    $options['identifier']
                )
            );
        } else {
            $builder-&gt;addViewTransformer(
                new EntityToPrimaryKeyTransformer(
                    $this-&gt;em-&gt;getRepository($options['class']),
                    $options['get_pk_callback']
                )
            );
        }
    }

    /**
     * See class docblock for description of options
     *
     * {@inheritdoc}
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver-&gt;setDefaults(array(
            'get_pk_callback' =&gt; function($entity) {
                return $entity-&gt;getId();
            },
            'multiple' =&gt; false,
            'identifier' =&gt; 'id',
            'data_class' =&gt; null,
        ));

        $resolver-&gt;setRequired(array('class'));
    }

    public function getName()
    {
        return 'hidden_entity';
    }

    /**
     * {@inheritdoc}
     */
    public function getParent()
    {
        return 'hidden';
    }
}
</code></pre>

<p>This works, it's straightforward, and for the most part looks like like all the examples you see for adding data transformers to a form type. Until you get to unit testing. See the problem? The transformers can't be mocked. "But wait!" you say, "Unit tests for Symfony forms are integration tests, they're supposed to make sure the transformers don't fail. Even says so <a href="http://symfony.com/doc/current/form/unit_testing.html" rel="noreferrer">in the documentation</a>!"</p>

<blockquote>
  <p>This test checks that none of your data transformers used by the form
  failed. The isSynchronized() method is only set to false if a data
  transformer throws an exception</p>
</blockquote>

<p>Ok, so then you live with the fact you can't isolate the transformers. No big deal? </p>

<p>Now consider what happens when unit testing a form that has a field of this type (assume that <code>HiddenEntityType</code> has been defined &amp; tagged in the service container)</p>

<pre><code>class SomeOtherFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            -&gt;add('field', 'hidden_entity', array(
                'class' =&gt; 'AppBundle:EntityName',
                'multiple' =&gt; true,
            ));
    }

    /* ... */
}
</code></pre>

<p>Now enters the problem. The unit test for <code>SomeOtherFormType</code> now needs to implement <code>getExtensions()</code> in order for the <code>hidden_entity</code> type to function. So how does that look?</p>

<pre><code>protected function getExtensions()
{
    $mockEntityManager = $this
        -&gt;getMockBuilder('Doctrine\ORM\EntityManager')
        -&gt;disableOriginalConstructor()
        -&gt;getMock();

    /* Expectations go here */

    return array(
        new PreloadedExtension(
            array('hidden_entity' =&gt; new HiddenEntityType($mockEntityManager)),
            array()
        )
    );
}
</code></pre>

<p>See where that comment is in the middle? Yeah, so for this to work correctly, all of the mocks and expectations that are in the unit test class for the  <code>HiddenEntityType</code> now effectively need to be duplicated here. I'm not OK with this, so what are my options?</p>

<ol>
<li><p>Inject the transformer as one of the options</p>

<p>This would be very straightforward and would make mocking simpler, but ultimately just kicks the can down the road. Because in this scenario, <code>new EntityToPrimaryKeyTransformer()</code> would just move from one form type class to another. Not to mention that I feel form types <em>should</em> hide their internal complexity from the rest of the system. This option means pushing that complexity outside the boundary of the form type.</p></li>
<li><p>Inject a transformer factory of sorts into the form type</p>

<p>This is a more typical approach to removing "newables" from within a method, but I can't shake the feeling that this is being done just to make the code testable, and is not actually making the code better. But if that was done, it would look something like this</p>

<pre><code>class HiddenEntityType extends AbstractType
{
    /**
     * @var DataTransformerFactory 
     */
    protected $transformerFactory;

    public function __construct(DataTransformerFactory $transformerFactory)
    {
        $this-&gt;transformerFactory = $transformerFactory;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder-&gt;addViewTransformer(
            $this-&gt;transformerFactory-&gt;createTransfomerForType($this, $options);
        );
    }

    /* Rest of type unchanged */
}
</code></pre>

<p>This feels ok until I consider what the factory will actually look like. It will need the entity manager injected, for starters. But what then? If I look further down the road, this supposedly-generic factory could need all sorts of dependencies for creating data transformers of different kinds. That is clearly not a good long-term design decision. So then what? Re-label this as an <code>EntityManagerAwareDataTransformerFactory</code>? It's starting to feel messy in here.</p></li>
<li><p>Stuff I'm not thinking of...</p></li>
</ol>

<p>Thoughts? Experiences? Solid advice?</p>
