2

I am trying to test a class that extends Symfony\Component\Form\AbstractType, and having trouble testing the required buildForm method with a PHPUnit mock object:

class PageType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name', 'text');
    }

    // ...
 }

Here's some example test code:

public function testBuildForm()
{
    $form = new PageType($this->manager);
    $formBuilder = $this->getMock('Symfony\Component\Form\FormBuilderInterface');
    $form->buildForm($formBuilder, array());
}

Unfortunately the call to getMock() fails:

PHP Fatal error:  Class Mock_FormBuilderInterface_f36f83d4 must implement interface Traversable as part of either Iterator or IteratorAggregate in Unknown on line 0

I suspect I might have fallen foul of this bug: https://github.com/sebastianbergmann/phpunit/issues/604

So, I have created an interface to work around the problem:

use Symfony\Component\Form\FormBuilderInterface;

interface FormBuilderInterfaceExtender extends \Iterator, FormBuilderInterface
{
}

and changed my test code:

public function testBuildForm()
{
    $form = new PageType($this->manager);
    $formBuilder = $this->getMock('Pwn\ContentBundle\Tests\Helper\FormBuilderInterfaceExtender');
    $form->buildForm($formBuilder, array());
}

Now I have a FormBuilder instance, but PHP doesn't see it as an instance of Symfony\Component\Form\FormBuilderInterface, so the type hinting in $form->buildForm() gives this error:

Argument 1 passed to Pwn\ContentBundle\Form\Type\PageType::buildForm() must be an instance of Symfony\Component\Form\FormBuilderInterface, instance of Mock_FormBuilderInterfaceExtender_3527f313 given

In other cases when I've used a mock object, type hinting and instanceof work correctly, but here it doesn't:

var_dump($formBuilder);
var_dump($formBuilder instanceof \Symfony\Component\Form\FormBuilderInterface);

gives

class Mock_FormBuilderInterfaceExtender_76df04af#19 (1) {
  private $__phpunit_invocationMocker =>
  NULL
}
bool(false)

It seems I can make this work by mocking a class that implements the interface:

$formBuilder = $this->getMockBuilder('Symfony\Component\Form\FormBuilder')
                    ->disableOriginalConstructor()
                    ->getMock();

var_dump($formBuilder instanceof \Symfony\Component\Form\FormBuilderInterface);
// ^ gives bool(true)

However, should I be able to use the interface itself? Why does extending an interface 'lose' the type hinting?

fazy
  • 2,095
  • 18
  • 30
  • Have you tried `assertInstanceOf` instead of `instanceof`? Have a peak at [this thread](http://stackoverflow.com/questions/3250503/phpunit-mocked-interfaces-and-instanceof). – quickshiftin Jan 04 '13 at 17:22
  • Just tried it, but assertInstanceOf is just a convenient way of using instanceof, so the effect is the same. – fazy Jan 04 '13 at 17:34
  • Thought there might have been some magic in there (you never know w/ phpUnit)! I put together a litmus test (see answer below) and seems like this should work so probably something subtle. – quickshiftin Jan 04 '13 at 18:24
  • 1
    Have you tried `getMock('Symfony\Component\Form\Tests\FormBuilderInterface')` ? – meze Jan 04 '13 at 22:05

2 Answers2

2

Whenever you can't test a Symfony2 interface, look in the component's Tests directory to see if there is a mock specifically for testing. The test interfaces extend the real interface, and add any tweaks required to make phpunit run them.

In your case, try mocking Symfony\Component\Form\Tests\FormBuilderInterface instead.

Adrian Schneider
  • 7,239
  • 1
  • 19
  • 13
  • Thanks, that fixed it, I'll look out for those test classes if I run into something like this again. – fazy Jan 07 '13 at 16:20
1

Seems like PHPUnit can Mock interfaces just fine, and has no problem with interfaces that extend other interfaces. Take a look at this class & TestCase

NamespaceA.php

<?php
namespace Testing;
interface NamespaceA {}

TestMockInterfaces.php

<?php
require 'NamespaceA.php';

interface Custom extends Testing\NamespaceA, Iterator, ArrayAccess {}

class TestMockInterfaces extends PHPUnit_Framework_TestCase
{
    public function testMocks()
    {
        $oSolidMock = $this->getMock('Iterator');
        $this->assertTrue($oSolidMock instanceof Iterator);

        $oSketchyMock = $this->getMock('Custom');
        $this->assertTrue($oSketchyMock instanceof Iterator);
        $this->assertTrue($oSketchyMock instanceof ArrayAccess);
        $this->assertTrue($oSketchyMock instanceof \Testing\NamespaceA);
    }
}

This passes 100%, so I suspect there's something else going on.

After you mock FormBuilderInterfaceExtender try var_dump(class_implements($formBuilder)); and see if it's what you expect.

quickshiftin
  • 66,362
  • 10
  • 68
  • 89
  • 1
    Thanks, very helpful... although Adrian's answer solved the actual problem, I hadn't come across class_implements before; this made it much easier to see what was going on. – fazy Jan 07 '13 at 16:22
  • Well, you should try to mock an interface that extends Traversable. – Frederik Krautwald Jul 29 '14 at 22:02