I want to unit test my own Views handlers, but Views' base classes depend on the global scope too much. I do not have time to write a unit test for \Drupal\views\Plugin\views\HandlerBase myself, but I am hoping that a green test result and my feedback about my contrib tests passing will be enough to get this issue fixed.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Xano’s picture

Issue tags: +PHPUnit
FileSize
2.5 KB

Initial patch. There will likely be more changes.

dawehner’s picture

Status: Active » Reviewed & tested by the community

Certainly we have a lot of tasks to solve before we can unit tests these classes, but this is some sort of good start.

Xano’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
6.47 KB

I added wrappers to get the module handler and Views data services, and replaced the calls to procedural string manipulation functions with calls to their OOP replacements.

Status: Needs review » Needs work

The last submitted patch, 3: drupal_2174021_3.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review

3: drupal_2174021_3.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 3: drupal_2174021_3.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
933 bytes
6.77 KB
damiankloip’s picture

I'm sorry but I don't see how having these 'wrapper' methods makes testing any easier?

Xano’s picture

Isn't the policy to wrap calls to \Drupal in protected methods, store the retrieved services in protected properties, and test by building a custom container in the test (#2171315: Ensure a NULL container in UnitTestCase::setUP proves that's a bad idea) or mock the class under test and let the wrapper methods return mocked services rather than calling \Drupal::service()?

damiankloip’s picture

What's wrong with mocking the container? IMO that is better than mocking the class under test. I don't really like that unless we are testing an abstract class.

dawehner’s picture

On the longrun I would suggest to use setter injection here, which we could do in our plugin/handler manager automatically

damiankloip’s picture

Yeah, we talked about that in irc. As I suggested it originally I am certainly in favour of that! :)

Xano’s picture

I agree on using setter injection if we really can't use constructor injection, but seeing as such setters are only relevant for tests and this patch does not actually introduce a test, I'm honestly not sure whether we should add those setters in this patch.

Xano’s picture

which we could do in our plugin/handler manager automatically

Adding implementation logic to plugin managers sounds like a Very Bad Idea™, especially because that would also require us to extend interfaces with setters, which completely defeats the purpose of having those interfaces in the first place (separation of contract and implementation).

damiankloip’s picture

Not sure why that comes into it? Which interfaces?

A. What happens in views plugin manager getHandler method is its own business.
B. Views doesn't really use interfaces for handlers.

Xano’s picture

On the longrun I would suggest to use setter injection here, which we could do in our plugin/handler manager automatically

If the manager calls methods on plugins, those plugins must implement an interface that defines those methods, otherwise we would be relying on implementation details. Setter methods for dependencies should not belong on interfaces, as dependencies are more often than not implementation details, and we can't couple those to an interface.

damiankloip’s picture

Well we don't even have to do this in the plugin manager. End of argument.

Xano’s picture

Back to the last patch, what is your verdict?

Xano’s picture

FileSize
6.35 KB

Re-roll.

dawehner’s picture

FileSize
12.71 KB
7.35 KB

Xano already had an opinion about the following suggestion, @damian what do you think about it?

Xano’s picture

So much for decoupling interfaces and implementations, but if this it what it takes to make this unit-testable, let's get it over with.

damiankloip’s picture

We don't really use an interface for handlers, its all about extending the base class - which this does. If for some insane reason you wanted to write a views handler from scratch, You don't get this anyway.

dawehner’s picture

So damian, xano, can we get this RTBC?

Xano’s picture

Sure. I just don't think I'm allowed to RTBC this, seeing as I wrote most of the code. The honor is therefore all yours :)

dawehner’s picture

Rules are there to be broken :p

Xano’s picture

Status: Needs review » Reviewed & tested by the community

Victory!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

vdc-2174021.patch no longer applies.

error: patch failed: core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php:169
error: core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php: patch does not apply

Xano’s picture

Status: Needs work » Needs review
FileSize
12.5 KB

Re-roll.

diff a/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php	(rejected hunks)
@@ -169,11 +186,11 @@ public function isOptional() {
    */
   public function adminLabel($short = FALSE) {
     if (!empty($this->options['admin_label'])) {
-      $title = check_plain($this->options['admin_label']);
+      $title = String::checkPlain($this->options['admin_label']);
       return $title;
     }
     $title = ($short && isset($this->definition['title short'])) ? $this->definition['title short'] : $this->definition['title'];
-    return t('!group: !title', array('!group' => $this->definition['group'], '!title' => $title));
+    return $this->t('!group: !title', array('!group' => $this->definition['group'], '!title' => $title));
   }
 
   /**
Xano’s picture

Status: Needs review » Reviewed & tested by the community

RTBC, provided the tests pass.

Sutharsan’s picture

Issue tags: -Needs reroll

'Needs reroll' tag removed.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

drupal_2174021_28.patch no longer applies.

error: patch failed: core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php:293
error: core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php: patch does not apply

damiankloip’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
12.03 KB

Rerolled.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Rertbc

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

2174021-32.patch no longer applies.

error: patch failed: core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php:11
error: core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php: patch does not apply

Xano’s picture

Status: Needs work » Needs review
FileSize
12.03 KB

Re-roll. RTBC, provided that the tests pass.

Xano’s picture

Status: Needs review » Reviewed & tested by the community

RTBC, as this was the reject:

diff a/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php	(rejected hunks)
@@ -11,16 +11,33 @@
 use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Utility\Url;
 use Drupal\Component\Utility\Xss;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Render\Element;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\PluginBase;
 use Drupal\views\ViewExecutable;
 use Drupal\Core\Database\Database;
 use Drupal\views\Views;
+use Drupal\views\ViewsData;
 
 abstract class HandlerBase extends PluginBase {
 
   /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The views data service.
+   *
+   * @var \Drupal\views\ViewsData
+   */
+  protected $viewsData;
+
+  /**
    * Where the $query object will reside:
    *
    * @var \Drupal\views\Plugin\views\query\QueryPluginBase

Sutharsan’s picture

Issue tags: -Needs reroll
alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

drupal_2174021_35.patch no longer applies.

error: patch failed: core/modules/views/lib/Drupal/views/Plugin/ViewsHandlerManager.php:9
error: core/modules/views/lib/Drupal/views/Plugin/ViewsHandlerManager.php: patch does not apply
error: patch failed: core/modules/views/views.services.yml:4
error: core/modules/views/views.services.yml: patch does not apply

Xano’s picture

Status: Needs work » Needs review
FileSize
7.17 KB

Re-roll. RTBC, under the condition that the tests pass, because the rejects were only about injecting the module handler into ViewsHandlerManager, which is already done in HEAD these days.

The rejects:

diff a/core/modules/views/views.services.yml b/core/modules/views/views.services.yml	(rejected hunks)
@@ -4,10 +4,10 @@ services:
     arguments: [access, '@container.namespaces', '@cache.views_info', '@language_manager', '@module_handler']
   plugin.manager.views.area:
     class: Drupal\views\Plugin\ViewsHandlerManager
-    arguments: [area, '@container.namespaces', '@views.views_data']
+    arguments: [area, '@container.namespaces', '@views.views_data', '@module_handler']
   plugin.manager.views.argument:
     class: Drupal\views\Plugin\ViewsHandlerManager
-    arguments: [argument, '@container.namespaces', '@views.views_data']
+    arguments: [argument, '@container.namespaces', '@views.views_data', '@module_handler']
   plugin.manager.views.argument_default:
     class: Drupal\views\Plugin\ViewsPluginManager
     arguments: [argument_default, '@container.namespaces', '@cache.views_info', '@language_manager', '@module_handler']
@@ -28,13 +28,13 @@ services:
     arguments: [exposed_form, '@container.namespaces', '@cache.views_info', '@language_manager', '@module_handler']
   plugin.manager.views.field:
     class: Drupal\views\Plugin\ViewsHandlerManager
-    arguments: [field, '@container.namespaces', '@views.views_data']
+    arguments: [field, '@container.namespaces', '@views.views_data', '@module_handler']
   plugin.manager.views.filter:
     class: Drupal\views\Plugin\ViewsHandlerManager
-    arguments: [filter, '@container.namespaces', '@views.views_data']
+    arguments: [filter, '@container.namespaces', '@views.views_data', '@module_handler']
   plugin.manager.views.join:
     class: Drupal\views\Plugin\ViewsHandlerManager
-    arguments: [join, '@container.namespaces', '@views.views_data']
+    arguments: [join, '@container.namespaces', '@views.views_data', '@module_handler']
   plugin.manager.views.pager:
     class: Drupal\views\Plugin\ViewsPluginManager
     arguments: [pager, '@container.namespaces', '@cache.views_info', '@language_manager', '@module_handler']
@@ -43,13 +43,13 @@ services:
     arguments: [query, '@container.namespaces', '@cache.views_info', '@language_manager', '@module_handler']
   plugin.manager.views.relationship:
     class: Drupal\views\Plugin\ViewsHandlerManager
-    arguments: [relationship, '@container.namespaces', '@views.views_data']
+    arguments: [relationship, '@container.namespaces', '@views.views_data', '@module_handler']
   plugin.manager.views.row:
     class: Drupal\views\Plugin\ViewsPluginManager
     arguments: [row, '@container.namespaces', '@cache.views_info', '@language_manager', '@module_handler']
   plugin.manager.views.sort:
     class: Drupal\views\Plugin\ViewsHandlerManager
-    arguments: [sort, '@container.namespaces', '@views.views_data']
+    arguments: [sort, '@container.namespaces', '@views.views_data', '@module_handler']
   plugin.manager.views.style:
     class: Drupal\views\Plugin\ViewsPluginManager
     arguments: [style, '@container.namespaces', '@cache.views_info', '@language_manager', '@module_handler']
diff a/core/modules/views/lib/Drupal/views/Plugin/ViewsHandlerManager.php b/core/modules/views/lib/Drupal/views/Plugin/ViewsHandlerManager.php	(rejected hunks)
@@ -9,9 +9,11 @@
 
 use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\Factory\ContainerFactory;
 use Drupal\Core\Plugin\Discovery\CacheDecorator;
 use Drupal\views\Plugin\Discovery\ViewsHandlerDiscovery;
+use Drupal\views\Plugin\views\HandlerBase;
 use Drupal\views\ViewsData;
 
 /**
@@ -44,9 +53,11 @@ class ViewsHandlerManager extends PluginManagerBase {
    *   An object that implements \Traversable which contains the root paths
    *   keyed by the corresponding namespace to look for plugin implementations,
    * @param \Drupal\views\ViewsData $views_data
-    *   The views data cache.
+   *   The views data cache.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
    */
-  public function __construct($handler_type, \Traversable $namespaces, ViewsData $views_data) {
+  public function __construct($handler_type, \Traversable $namespaces, ViewsData $views_data, ModuleHandlerInterface $module_handler) {
     $this->discovery = new ViewsHandlerDiscovery($handler_type, $namespaces);
     $this->discovery = new CacheDecorator($this->discovery, "views:$handler_type", 'views_info');
 
@@ -54,6 +65,7 @@ public function __construct($handler_type, \Traversable $namespaces, ViewsData $
 
     $this->viewsData = $views_data;
     $this->handlerType = $handler_type;
+    $this->moduleHandler = $module_handler;
   }
Xano’s picture

Status: Needs review » Reviewed & tested by the community
Alumei’s picture

Issue tags: -Needs reroll
alexpott’s picture

Status: Reviewed & tested by the community » Needs review

Are these ever serialised? Do we need to worry about serialising the data and moduleHandler?

Xano’s picture

As far as I know, serializing plugin instances is a Bad Idea™ and unsupported by design. If you want to reproduce an instance later, save its ID and configuration instead.

dawehner’s picture

I kind of fear that the views forms will contain references to the view object and so also to the instances of the plugins.

Xano’s picture

We can either not store dependencies in plugin class properties (easy solution), or prevent view objects from serializing the plugins they contain.

dawehner’s picture

Oh, Drupal\Core\Plugin\PluginBase already is serializable-aware.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

So for me the patch seems to be safe.

scor’s picture

39: drupal_2174021_39.patch queued for re-testing.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 39: drupal_2174021_39.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review
FileSize
6.9 KB

Re-roll, because of recent additions to Unicode. Reject log:

diff a/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php	(rejected hunks)
@@ -259,11 +276,11 @@ protected function caseTransform($string, $option) {
       default:
         return $string;
       case 'upper':
-        return drupal_strtoupper($string);
+        return Unicode::strtoupper($string);
       case 'lower':
-        return drupal_strtolower($string);
+        return Unicode::strtolower($string);
       case 'ucfirst':
-        return drupal_strtoupper(drupal_substr($string, 0, 1)) . drupal_substr($string, 1);
+        return Unicode::strtoupper(Unicode::substr($string, 0, 1)) . Unicode::substr($string, 1);
       case 'ucwords':
         if (Unicode::getStatus() == Unicode::STATUS_MULTIBYTE) {
           return mb_convert_case($string, MB_CASE_TITLE);
Xano’s picture

Status: Needs review » Reviewed & tested by the community

RTBC, under the condition that the tests pass.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 50: drupal_2174021_50.patch, failed testing.

Xano’s picture

Status: Needs work » Needs review

50: drupal_2174021_50.patch queued for re-testing.

Xano’s picture

Status: Needs review » Reviewed & tested by the community

The tests failed because of a random segmentation fault. RTBC'ing as per #33 and #51.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

drupal_2174021_50.patch no longer applies.

error: patch failed: core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php:259
error: core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php: patch does not apply

Xano’s picture

Status: Needs work » Needs review
FileSize
7.12 KB

Re-roll. Reject:

diff a/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php	(rejected hunks)
@@ -259,9 +276,9 @@ protected function caseTransform($string, $option) {
       default:
         return $string;
       case 'upper':
-        return drupal_strtoupper($string);
+        return Unicode::strtoupper($string);
       case 'lower':
-        return drupal_strtolower($string);
+        return Unicode::strtolower($string);
       case 'ucfirst':
         return Unicode::ucfirst($string);
       case 'ucwords':
Xano’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs reroll

RTBC as per #54, under the condition that the tests pass.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 56: drupal_2174021_56.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
6.61 KB

Rerolled.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 59: drupal-2174021-59.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
6.4 KB

Reroll.

Xano’s picture

Status: Needs review » Reviewed & tested by the community

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 62: drupal-2174021-62.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
6.39 KB

Really ...

Xano’s picture

Status: Needs review » Reviewed & tested by the community
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 9696e51 and pushed to 8.x. Thanks!

diff --git a/core/modules/views/lib/Drupal/views/Plugin/ViewsHandlerManager.php b/core/modules/views/lib/Drupal/views/Plugin/ViewsHandlerManager.php
index bf231a5..dae3026 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/ViewsHandlerManager.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/ViewsHandlerManager.php
@@ -130,6 +130,9 @@ public function getHandler($item, $override = NULL) {
     return $this->createInstance('broken', array('optional' => $optional, 'original_configuration' => $item));
   }
 
+  /**
+   * {@inheritdoc}
+   */
   public function createInstance($plugin_id, array $configuration = array()) {
     $instance = parent::createInstance($plugin_id, $configuration);
     if ($instance instanceof HandlerBase) {
@@ -139,5 +142,4 @@ public function createInstance($plugin_id, array $configuration = array()) {
     return $instance;
   }
 
-
 }

Fixed in commit.

  • Commit 9696e51 on 8.x by alexpott:
    Issue #2174021 by Xano, dawehner, damiankloip: Make \Drupal\views\Plugin...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.