【问题标题】:How to really unit test Symfony forms?如何真正对 Symfony 表单进行单元测试?
【发布时间】:2018-01-03 17:24:54
【问题描述】:

以下示例来自official documentation

use AppBundle\Form\Type\TestedType;
use AppBundle\Model\TestObject;
use Symfony\Component\Form\Test\TypeTestCase;

class TestedTypeTest extends TypeTestCase
{
    public function testSubmitValidData()
    {
        $formData = array(
            'test' => 'test',
            'test2' => 'test2',
        );

        $form = $this->factory->create(TestedType::class);

        $object = TestObject::fromArray($formData);

        // submit the data to the form directly
        $form->submit($formData);

        $this->assertTrue($form->isSynchronized());
        $this->assertEquals($object, $form->getData());

        $view = $form->createView();
        $children = $view->children;

        foreach (array_keys($formData) as $key) {
            $this->assertArrayHasKey($key, $children);
        }
    }
}

但是,使用真正的单元测试方法,测试应该只包含一个单独的类作为SUT,其他所有内容都应该是Test Doubles,如存根、模拟对象...

我们应该如何使用像模拟对象这样的测试替身对 Symfony 表单进行单元测试?

我们可以假设一个简单的表单类:

class TestedType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('firstname', TextType::class, [
                'label' => 'First name',
                'attr' => [
                    'placeholder' => 'John Doe',
                ],
            ])
    }
}

【问题讨论】:

    标签: php symfony unit-testing mocking phpunit


    【解决方案1】:

    您的问题很老,但没有得到解答,我在最近遇到同样的问题时偶然发现了它。

    我们应该如何使用像模拟对象这样的测试替身对 Symfony 表单进行单元测试?

    我们通常测试单个方法的结果,但是在 buildForm 方法的情况下,我们可以用简单的表单类型编写的唯一测试是 interactionFormBuilderInterface 实例作为参数传递。

    // Imports and PhpDoc annotations skipped for brievety
    public class TestedTypeTest  extends TestCase
    {
        private $systemUnderTest;
    
        protected function setUp()
        {
            parent::setUp();
            $this->systemUnderTest = new TestedType();
        }
    
        /**
         * Tests that form is correctly build according to specs
         */
        public function testBuildForm(): void
        {
            $formBuilderMock = $this->createMock(FormBuilderInterface::class);
            $formBuilderMock->expects($this->atLeastOnce())->method('add')->willReturnSelf();
    
            // Passing the mock as a parameter and an empty array as options as I don't test its use
            $this->systemUnderTest->buildForm($formBuilderMock, []);
        }
    }
    

    所以正确地说,这是一个非常基本的单元测试,单独测试你的类。

    不要犯同样的错误,也不要忘记调用 willReturnSelf 方法,因为 add可链接方法 (@ 987654321@).

    您可以从这里开始并完善您的测试,将其更多地与实施联系起来

    // Tests number of calls to add method, in my case, 2
    $formBuilderMock->expects($this->exactly(2))->method('add')->willReturnSelf();
    

    // Tests number of calls AND parameters successively passed
    $formBuilderMock->expects($this->exactly(2))->method('add')->withConsecutive(
        [$this->equalTo('field_1'), $this->equalTo(TextType::class)],
        [$this->equalTo('field_2'), $this->equalTo(TextType::class)]
    );
    

    我想你明白了......在那里,你的测试与实现细节相关联,迫使你在更改代码后立即更改它们:它是你改变测试粒度的调用,具体取决于你的上下文.


    旁注

    由于您没有指定 Symfony 和 PhpUnit 的任何版本,请知道我的示例使用以下版本:

    • Symfony 4.1
    • PhpUnit 7.5

    在回答有关测试行为时阅读的问题

    Do mocks break the "test the interface, not the implementation" mantra?
    What's the difference between faking, mocking, and stubbing?

    【讨论】:

      猜你喜欢
      • 2014-01-28
      • 1970-01-01
      • 1970-01-01
      • 2018-09-19
      • 2018-09-27
      • 2014-09-21
      • 1970-01-01
      • 1970-01-01
      • 2012-01-08
      相关资源
      最近更新 更多