用表格编写控制器的测试

时间:2017-03-16 10:24:32

标签: php forms unit-testing zend-framework zend-framework3

自从我开始使用Zend Framework 3以来,我在测试控制器方面遇到了问题。我试图用PhpUnit 5.7测试我的控制器,我的控制器依赖于Zend Form,它与Doctrine的DoctrineObject一起使用。 我试图尽可能地简化这一点,所以这里是一个让我头痛的设置的最小例子:

控制器:

class IndexController extends AbstractActionController {
    private $form;
    public function __construct(AlbumForm $form) {
        $this->form = $form;
    }
    public function indexAction() {
        return ['form' => $this-form];
    }
}

的ControllerFactory:

class IndexControllerFactory implements FactoryInterface {
    public function __invoke(ContainerInterface $container, ...) {
        $formManager = $container->get('FormElementManager');
        return new IndexController($formManager->get(AlbumForm::class));
    }
}

albums/index/index.phtml中的相应视图模板:

<?php
$this->form->prepare();
$this->form->setAttribute('action', $this->url(null, [], true));

$albumFieldset = $this->form->get('album');
?>
<?= $this->form()->openTag($this-form) ?>
    <div class="form-group">
        <?= $this->formRow($albumFieldset->get('name')) ?>
    </div>
<?= $this->form()->closeTag() ?>

表格:

class AlbumForm extends Form {
    public function init() {
        $this->add([
            'name' => 'albumFieldset',
            'type' => AlbumFieldset::class,
            'options' => [
                'use_as_base_fieldset' => true,
            ],
        ]);
    }
}

字段集:

class AlbumFieldset extends Fieldset {
    public function init() {
        $this->add([
            'name' => 'name',
            'type' => Text::class,
            'options' => [
                'label' => 'Name of album',
            ],
        ]);
    }
}

FieldsetFactory:

class AlbumFieldsetFactory implements FactoryInterface {
    public function __invoke(ContainerInterface $container, ...) {
        $objectManager = $container->get(ObjectManager::class);
        $fieldset = new AlbumFieldset();
        $fieldset->setHydrator(new DoctrineObject($objectManager));
        $fieldset->setObject(new Album());
        return $fieldset;
    }
}

现在,到目前为止,一切都很顺利 但是,在为此编写测试时,我遇到了麻烦。让我先向您展示我到目前为止所拥有的内容:

class IndexControllerTest extends AbstractHttpControllerTestCase {
    protected function setUp() {
        parent::setUp();
        $this->configureServiceManager($this->getApplicationServiceLocator());
    }
    private function configureServiceManager(ServiceManager $services) {
        $services->setAllowOverride(true);

        $services->setService(ObjectManager::class, $this->mockObjectManager()->reveal());
        $services->setService('FormElementManager', $this->mockFormManager()->reveal());

        $services->setAllowOverride(false);
    }
    private $objectManager;
    private function mockObjectManager() {
        $this->objectManager = $this->prophesize(ObjectManager::class);
        return $this->objectManager;
    }
    private $formManager;
    private function mockFormManager() {
        $this->formManager = $this->prophesize(FormElementManager::class);
        $this->formManager->get(AlbumForm::class)->willReturn($this->mockForm()->reveal());
        return $this->formManager;
    }
    private $form;
    private function mockForm() {
        $this->form = $this->prophesize(AlbumForm::class);
        $this->form->prepare()->willReturn(null);
        $this->form->setAttribute('action', Argument::type('string'))->willReturn(null);
        $this->form->getAttributes()->willReturn([]);
        $this->form->get('album')->willReturn($this->mockAlbumFieldset()->reveal());
        return $this->form;
    }
    private $albumFieldset;
    private function mockAlbumFieldset() {
        $this->albumFieldset = $this->prophesize(AlbumFieldset::class);
        $this->albumFieldset->get('name')->willReturn($this->mockName()->reveal());
        return $this->albumFieldset;
    }
    private $name;
    private function mockName() {
        $this->name = $this->prophesize(Text::class);
        $this->name->getLabel()->willReturn('label');
        $this->name->getLabelAttributes()->willReturn(['for' => 'name']);
        $this->name->getLabelOption('disable_html_escape')->willReturn(false);
        $this->name->getLabelOption('always_wrap')->willReturn(false);
        $this->name->getLabelOption('label_position')->willReturn('prepend');
        $this->name->getName('album[name]');
        $this->name->getAttribute('type')->willReturn('text');
        $this->name->hasAttribute('id')->willReturn(true);
        $this->name->getAttribute('id')->willReturn('name');
        $this->name->getAttributes([])->willReturn([]);
        $this->name->getValue()->willReturn(null);
        $this->name->getMessages()->willReturn([]);
        return $this->name;
    }
}

这最终会毫无错误地运行。但是,我想提请您注意最后几种方法,尤其是mockName()。这些定义中的大多数都是完全默认的,并且几乎没有一个在开头的AlbumFieldset中指定(仅名称为)。将它们写入每个表单输入是非常烦人的,写下来实际上会引入比它解决的更多错误。例如,我仍然不确定always_wrap的正确标签选项是什么。我实际上并不关心这个选项,但我必须在测试中写一些关于它的内容,因为否则测试会因'Prophecy\Exception\Call\UnexpectedCallException' with message 'Method call: - getLabelOption("always_wrap") on Double\Zend\Form\Element\Text\P245 was not expected, expected calls were: ...而失败。

因此,我问你:有什么更好的办法吗?一种不涉及为我的fieldset中的每个字段写20多行的方法。如果它涉及重写我的控制器/字段集/视图模板(等),那就完全没问题了!

非常感谢任何帮助!此外,这是我第一次在一个论坛中用超过八年的编程问一些东西,所以如果有什么不清楚的话,请耐心等待。

此致 斯特芬

PS:我已经尝试过的是给IndexController null而不是实际的表单,并在检测到表单为空时简单地中止视图模板。然而,虽然没有那么多的设置,但我基本上只是避免了视图模板的逻辑。因此,我无法检测视图模板中的错误。那不是我想要的。

1 个答案:

答案 0 :(得分:0)

编辑IndexControllerTest:将private更改为其他位置的protected并将其扩展为新字段。每个新控制器必须覆盖调用parent :: methodname($ args)的方法并添加所需的代码......