Issue #1987716 by pfrenssen, tim.plunkett, kgoel: Provide a FormTestBase class to allow PHPUnit form tests.

8.0.x
webchick 2014-01-13 19:38:51 -08:00
parent aa15f0ca32
commit eacd8a50b1
9 changed files with 457 additions and 369 deletions

View File

@ -620,7 +620,7 @@ class FormBuilder implements FormBuilderInterface {
// possibly ending execution. We make sure we do not react to the batch
// that is already being processed (if a batch operation performs a
// self::submitForm).
if ($batch =& batch_get() && !isset($batch['current_set'])) {
if ($batch = &$this->batchGet() && !isset($batch['current_set'])) {
// Store $form_state information in the batch definition.
// We need the full $form_state when either:
// - Some submit handlers were saved to be called during batch
@ -1195,7 +1195,7 @@ class FormBuilder implements FormBuilderInterface {
// Check if a previous _submit handler has set a batch, but make sure we
// do not react to a batch that is already being processed (for instance
// if a batch operation performs a self::submitForm()).
if ($type == 'submit' && ($batch =& batch_get()) && !isset($batch['id'])) {
if ($type == 'submit' && ($batch = &$this->batchGet()) && !isset($batch['id'])) {
// Some previous submit handler has set a batch. To ensure correct
// execution order, store the call in a special 'control' batch set.
// See _batch_next_set().
@ -1837,4 +1837,11 @@ class FormBuilder implements FormBuilderInterface {
$this->request = $request;
}
/**
* Wraps batch_get().
*/
protected function &batchGet() {
return batch_get();
}
}

View File

@ -656,17 +656,6 @@ class FormTest extends WebTestBase {
$this->assertTrue(!empty($element), 'The textarea has the proper required attribute.');
}
/**
* Tests error border of multiple fields with same name in a page.
*/
function testMultiFormSameNameErrorClass() {
$this->drupalGet('form-test/double-form');
$edit = array();
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertFieldByXpath('//input[@id="edit-name" and contains(@class, "error")]', NULL, 'Error input form element class found for first element.');
$this->assertNoFieldByXpath('//input[@id="edit-name--2" and contains(@class, "error")]', NULL, 'No error input form element class found for second element.');
}
/**
* Tests a form with a form state storing a database connection.
*/

View File

@ -1,45 +0,0 @@
<?php
/**
* @file
* Definition of Drupal\system\Tests\Form\HTMLIdTest.
*/
namespace Drupal\system\Tests\Form;
use Drupal\simpletest\WebTestBase;
/**
* Tests uniqueness of generated HTML IDs.
*/
class HTMLIdTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('form_test');
public static function getInfo() {
return array(
'name' => 'Unique HTML IDs',
'description' => 'Tests functionality of drupal_html_id().',
'group' => 'Form API',
);
}
/**
* Tests that HTML IDs do not get duplicated when form validation fails.
*/
function testHTMLId() {
$this->drupalGet('form-test/double-form');
$this->assertNoDuplicateIds('There are no duplicate IDs');
// Submit second form with empty title.
$edit = array();
$this->drupalPostForm(NULL, $edit, 'Save', array(), array(), 'form-test-html-id--2');
$this->assertNoDuplicateIds('There are no duplicate IDs');
}
}

View File

@ -2056,34 +2056,6 @@ function form_test_required_attribute($form, &$form_state) {
return $form;
}
/**
* Menu callback returns two instances of the same form.
*
* @deprecated \Drupal\form_test\Controller\FormTestController::doubleForm()
*/
function form_test_double_form() {
return array(
'form1' => drupal_get_form('form_test_html_id'),
'form2' => drupal_get_form('form_test_html_id'),
);
}
/**
* Builds a simple form to test duplicate HTML IDs.
*/
function form_test_html_id($form, &$form_state) {
$form['name'] = array(
'#type' => 'textfield',
'#title' => 'name',
'#required' => TRUE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Save',
);
return $form;
}
/**
* Builds a simple form to test form button classes.
*

View File

@ -77,14 +77,6 @@ form_test.wrapper:
requirements:
_access: 'TRUE'
form_test.double_form:
path: '/form-test/double-form'
defaults:
_title: 'Double form test'
_content: '\Drupal\form_test\Controller\FormTestController::doubleForm'
requirements:
_access: 'TRUE'
form_test.alter_form:
path: '/form-test/alter'
defaults:

View File

@ -42,11 +42,4 @@ class FormTestController extends ControllerBase {
return form_test_wrapper_callback($form_id);
}
/**
* @todo Remove form_test_double_form().
*/
public function doubleForm() {
return form_test_double_form();
}
}

View File

@ -8,68 +8,17 @@
namespace Drupal\Tests\Core\Form {
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Form\FormBuilder;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Tests the form builder.
*
* @group Drupal
* @group Form
*/
class FormBuilderTest extends UnitTestCase {
/**
* The form builder being tested.
*
* @var \Drupal\Core\Form\FormBuilder
*/
protected $formBuilder;
/**
* The mocked URL generator.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* The mocked module handler.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The expirable key value store used by form cache.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
*/
protected $formCache;
/**
* The expirable key value store used by form state cache.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
*/
protected $formStateCache;
/**
* The current user.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* The CSRF token generator.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Access\CsrfTokenGenerator
*/
protected $csrfToken;
class FormBuilderTest extends FormTestBase {
/**
* {@inheritdoc}
@ -82,39 +31,6 @@ class FormBuilderTest extends UnitTestCase {
);
}
public function setUp() {
$this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$this->formCache = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface');
$this->formStateCache = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface');
$key_value_expirable_factory = $this->getMockBuilder('\Drupal\Core\KeyValueStore\KeyValueExpirableFactory')
->disableOriginalConstructor()
->getMock();
$key_value_expirable_factory->expects($this->any())
->method('get')
->will($this->returnValueMap(array(
array('form', $this->formCache),
array('form_state', $this->formStateCache),
)));
$event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
$translation_manager = $this->getStringTranslationStub();
$this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
->disableOriginalConstructor()
->getMock();
$http_kernel = $this->getMockBuilder('Drupal\Core\HttpKernel')
->disableOriginalConstructor()
->getMock();
$this->formBuilder = new TestFormBuilder($this->moduleHandler, $key_value_expirable_factory, $event_dispatcher, $this->urlGenerator, $translation_manager, $this->csrfToken, $http_kernel);
$this->formBuilder->setRequest(new Request());
$this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->formBuilder->setCurrentUser($this->account);
}
/**
* Tests the getFormId() method with a string based form ID.
*/
@ -729,147 +645,6 @@ class FormBuilderTest extends UnitTestCase {
$this->formBuilder->buildForm($form_arg, $form_state);
}
/**
* Provides a mocked form object.
*
* @param string $form_id
* (optional) The form ID to be used. If none is provided, the form will be
* set with no expectation about getFormId().
* @param mixed $expected_form
* (optional) If provided, the expected form response for buildForm() to
* return. Defaults to NULL.
* @param int $count
* (optional) The number of times the form is expected to be built. Defaults
* to 1.
*
* @return \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Form\FormInterface
* The mocked form object.
*/
protected function getMockForm($form_id, $expected_form = NULL, $count = 1) {
$form = $this->getMock('Drupal\Core\Form\FormInterface');
$form->expects($this->once())
->method('getFormId')
->will($this->returnValue($form_id));
if ($expected_form) {
$form->expects($this->exactly($count))
->method('buildForm')
->will($this->returnValue($expected_form));
}
return $form;
}
/**
* Asserts that the expected form structure is found in a form for a given key.
*
* @param array $expected_form
* The expected form structure.
* @param array $actual_form
* The actual form.
* @param string|null $form_key
* (optional) The form key to look in. Otherwise the entire form will be
* compared.
*/
protected function assertFormElement(array $expected_form, array $actual_form, $form_key = NULL) {
$expected_element = $form_key ? $expected_form[$form_key] : $expected_form;
$actual_element = $form_key ? $actual_form[$form_key] : $actual_form;
$this->assertSame(array_intersect_key($expected_element, $actual_element), $expected_element);
}
}
/**
* Provides a test form builder class.
*/
class TestFormBuilder extends FormBuilder {
/**
* {@inheritdoc}
*/
protected function sendResponse(Response $response) {
parent::sendResponse($response);
// Throw an exception instead of exiting.
throw new \Exception('exit');
}
/**
* @param \Drupal\Core\Session\AccountInterface $account
*/
public function setCurrentUser(AccountInterface $account) {
$this->currentUser = $account;
}
/**
* {@inheritdoc}
*/
protected function getElementInfo($type) {
$types['token'] = array(
'#input' => TRUE,
);
$types['value'] = array(
'#input' => TRUE,
);
$types['select'] = array(
'#input' => TRUE,
'#multiple' => FALSE,
'#empty_value' => '',
);
$types['radios'] = array(
'#input' => TRUE,
);
$types['textfield'] = array(
'#input' => TRUE,
);
$types['submit'] = array(
'#input' => TRUE,
'#name' => 'op',
'#is_button' => TRUE,
);
if (!isset($types[$type])) {
$types[$type] = array();
}
return $types[$type];
}
/**
* {@inheritdoc}
*/
protected function drupalInstallationAttempted() {
return FALSE;
}
/**
* {@inheritdoc}
*/
protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
}
/**
* {@inheritdoc}
*/
protected function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG_NOTICE, $link = NULL) {
}
/**
* {@inheritdoc}
*/
protected function drupalHtmlClass($class) {
return $class;
}
/**
* {@inheritdoc}
*/
protected function drupalHtmlId($id) {
return $id;
}
/**
* {@inheritdoc}
*/
protected function drupalStaticReset($name = NULL) {
}
}
class TestForm implements FormInterface {
@ -892,48 +667,9 @@ class TestFormInjected extends TestForm implements ContainerInjectionInterface {
}
namespace {
function test_form_id() {
$form['test'] = array(
'#type' => 'textfield',
'#title' => 'Test',
);
$form['select'] = array(
'#type' => 'select',
'#options' => array(
'foo' => 'foo',
'bar' => 'bar',
),
);
$form['options'] = array(
'#type' => 'radios',
'#options' => array(
'foo' => 'foo',
'bar' => 'bar',
),
);
$form['value'] = array(
'#type' => 'value',
'#value' => 'bananas',
);
$form['actions'] = array(
'#type' => 'actions',
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
function test_form_id_custom_submit(array &$form, array &$form_state) {
}
if (!defined('WATCHDOG_ERROR')) {
define('WATCHDOG_ERROR', 3);
}
if (!function_exists('batch_get')) {
function &batch_get() {
$batch = array();
return $batch;
}
}
}

View File

@ -0,0 +1,366 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Form\FormTestBase.
*/
namespace Drupal\Tests\Core\Form {
use Drupal\Core\Form\FormBuilder;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Provides a base class for testing form functionality.
*
* @see \Drupal\Core\Form\FormBuilder
*/
abstract class FormTestBase extends UnitTestCase {
/**
* The form builder being tested.
*
* @var \Drupal\Core\Form\FormBuilderInterface
*/
protected $formBuilder;
/**
* The mocked URL generator.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* The mocked module handler.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The expirable key value store used by form cache.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
*/
protected $formCache;
/**
* The expirable key value store used by form state cache.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
*/
protected $formStateCache;
/**
* The current user.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* The CSRF token generator.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Access\CsrfTokenGenerator
*/
protected $csrfToken;
/**
* The request.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* The event dispatcher.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The expirable key value factory.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\KeyValueStore\KeyValueExpirableFactory
*/
protected $keyValueExpirableFactory;
/**
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\StringTranslation\TranslationInterface
*/
protected $translationManager;
/**
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\HttpKernel
*/
protected $httpKernel;
public function setUp() {
$this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$this->formCache = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface');
$this->formStateCache = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface');
$this->keyValueExpirableFactory = $this->getMockBuilder('Drupal\Core\KeyValueStore\KeyValueExpirableFactory')
->disableOriginalConstructor()
->getMock();
$this->keyValueExpirableFactory->expects($this->any())
->method('get')
->will($this->returnValueMap(array(
array('form', $this->formCache),
array('form_state', $this->formStateCache),
)));
$this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface');
$this->translationManager = $this->getStringTranslationStub();
$this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
->disableOriginalConstructor()
->getMock();
$this->httpKernel = $this->getMockBuilder('Drupal\Core\HttpKernel')
->disableOriginalConstructor()
->getMock();
$this->request = new Request();
$this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->setupFormBuilder();
}
/**
* {@inheritdoc}
*/
protected function tearDown() {
$this->formBuilder->drupalStaticReset();
}
/**
* Sets up a new form builder object to test.
*/
protected function setupFormBuilder() {
$this->formBuilder = new TestFormBuilder($this->moduleHandler, $this->keyValueExpirableFactory, $this->eventDispatcher, $this->urlGenerator, $this->translationManager, $this->csrfToken, $this->httpKernel);
$this->formBuilder->setRequest($this->request);
$this->formBuilder->setCurrentUser($this->account);
}
/**
* Provides a mocked form object.
*
* @param string $form_id
* (optional) The form ID to be used. If none is provided, the form will be
* set with no expectation about getFormId().
* @param mixed $expected_form
* (optional) If provided, the expected form response for buildForm() to
* return. Defaults to NULL.
* @param int $count
* (optional) The number of times the form is expected to be built. Defaults
* to 1.
*
* @return \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Form\FormInterface
* The mocked form object.
*/
protected function getMockForm($form_id, $expected_form = NULL, $count = 1) {
$form = $this->getMock('Drupal\Core\Form\FormInterface');
$form->expects($this->once())
->method('getFormId')
->will($this->returnValue($form_id));
if ($expected_form) {
$form->expects($this->exactly($count))
->method('buildForm')
->will($this->returnValue($expected_form));
}
return $form;
}
/**
* Simulates a form submission within a request, bypassing submitForm().
*
* Calling submitForm() will reset the form builder, if two forms were on the
* same page, they will be submitted simultaneously.
*
* @param string $form_id
* The unique string identifying the form.
* @param \Drupal\Core\Form\FormInterface $form_arg
* The form object.
* @param array $form_state
* An associative array containing the current state of the form.
*
* @return array
* The built form.
*/
protected function simulateFormSubmission($form_id, FormInterface $form_arg, array &$form_state) {
$form_state['build_info']['callback_object'] = $form_arg;
$form_state['build_info']['args'] = array();
$form_state['input']['op'] = 'Submit';
$form_state['programmed'] = TRUE;
$form_state['submitted'] = TRUE;
return $this->formBuilder->buildForm($form_id, $form_state);
}
/**
* Asserts that the expected form structure is found in a form for a given key.
*
* @param array $expected_form
* The expected form structure.
* @param array $actual_form
* The actual form.
* @param string|null $form_key
* (optional) The form key to look in. Otherwise the entire form will be
* compared.
*/
protected function assertFormElement(array $expected_form, array $actual_form, $form_key = NULL) {
$expected_element = $form_key ? $expected_form[$form_key] : $expected_form;
$actual_element = $form_key ? $actual_form[$form_key] : $actual_form;
$this->assertSame(array_intersect_key($expected_element, $actual_element), $expected_element);
}
}
/**
* Provides a test form builder class.
*/
class TestFormBuilder extends FormBuilder {
protected static $seenIds = array();
/**
* {@inheritdoc}
*/
protected function sendResponse(Response $response) {
parent::sendResponse($response);
// Throw an exception instead of exiting.
throw new \Exception('exit');
}
/**
* @param \Drupal\Core\Session\AccountInterface $account
*/
public function setCurrentUser(AccountInterface $account) {
$this->currentUser = $account;
}
/**
* {@inheritdoc}
*/
protected function getElementInfo($type) {
$types['token'] = array(
'#input' => TRUE,
);
$types['value'] = array(
'#input' => TRUE,
);
$types['radios'] = array(
'#input' => TRUE,
);
$types['textfield'] = array(
'#input' => TRUE,
);
$types['submit'] = array(
'#input' => TRUE,
'#name' => 'op',
'#is_button' => TRUE,
);
if (!isset($types[$type])) {
$types[$type] = array();
}
return $types[$type];
}
/**
* {@inheritdoc}
*/
protected function drupalInstallationAttempted() {
return FALSE;
}
/**
* {@inheritdoc}
*/
protected function menuGetItem() {
return FALSE;
}
/**
* {@inheritdoc}
*/
protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
}
/**
* {@inheritdoc}
*/
protected function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG_NOTICE, $link = NULL) {
}
/**
* {@inheritdoc}
*/
protected function drupalHtmlClass($class) {
return $class;
}
/**
* {@inheritdoc}
*/
protected function drupalHtmlId($id) {
if (isset(static::$seenIds[$id])) {
$id = $id . '--' . ++static::$seenIds[$id];
}
else {
static::$seenIds[$id] = 1;
}
return $id;
}
/**
* {@inheritdoc}
*/
public function drupalStaticReset($name = NULL) {
static::$seenIds = array();
}
/**
* {@inheritdoc}
*/
protected function &batchGet() {
$batch = array();
return $batch;
}
}
}
namespace {
function test_form_id() {
$form['test'] = array(
'#type' => 'textfield',
'#title' => 'Test',
);
$form['options'] = array(
'#type' => 'radios',
'#options' => array(
'foo' => 'foo',
'bar' => 'bar',
),
);
$form['value'] = array(
'#type' => 'value',
'#value' => 'bananas',
);
$form['actions'] = array(
'#type' => 'actions',
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Form\FormValidationTest.
*/
namespace Drupal\Tests\Core\Form;
/**
* Tests various form element validation mechanisms.
*
* @group Drupal
* @group Form
*/
class FormValidationTest extends FormTestBase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Form element validation',
'description' => 'Tests various form element validation mechanisms.',
'group' => 'Form API',
);
}
public function testNoDuplicateErrorsForIdenticalForm() {
$form_id = 'test_form_id';
$expected_form = $form_id();
$expected_form['test']['#required'] = TRUE;
// Mock a form object that will be built three times.
$form_arg = $this->getMockForm($form_id, $expected_form, 3);
// The first form will have errors.
$form_state = array();
$this->formBuilder->getFormId($form_arg, $form_state);
$this->simulateFormSubmission($form_id, $form_arg, $form_state);
$errors = $this->formBuilder->getErrors($form_state);
$this->assertNotEmpty($errors['test']);
// The second form will not have errors.
$form_state = array();
$this->simulateFormSubmission($form_id, $form_arg, $form_state);
$errors = $this->formBuilder->getErrors($form_state);
$this->assertEmpty($errors);
// Reset the form builder.
$this->setupFormBuilder();
// On a new request, the first form will have errors again.
$form_state = array();
$this->simulateFormSubmission($form_id, $form_arg, $form_state);
$errors = $this->formBuilder->getErrors($form_state);
$this->assertNotEmpty($errors['test']);
}
public function testUniqueHtmlId() {
$form_id = 'test_form_id';
$expected_form = $form_id();
$expected_form['test']['#required'] = TRUE;
// Mock a form object that will be built three times.
$form_arg = $this->getMockForm($form_id, $expected_form, 2);
$form_state = array();
$this->formBuilder->getFormId($form_arg, $form_state);
$form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
$this->assertSame($form_id, $form['#id']);
$form_state = array();
$form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
$this->assertSame("$form_id--2", $form['#id']);
}
}