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?

4
  • Have you tried assertInstanceOf instead of instanceof? Have a peak at this thread. Commented Jan 4, 2013 at 17:22
  • Just tried it, but assertInstanceOf is just a convenient way of using instanceof, so the effect is the same. Commented Jan 4, 2013 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. Commented Jan 4, 2013 at 18:24
  • 1
    Have you tried getMock('Symfony\Component\Form\Tests\FormBuilderInterface') ? Commented Jan 4, 2013 at 22:05

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, that fixed it, I'll look out for those test classes if I run into something like this again.
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.

2 Comments

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.
Well, you should try to mock an interface that extends Traversable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.