Our browser tests now need $defaultTheme

10x: Drupal\Tests\BrowserTestBase::$defaultTheme is required in drupal:9.0.0 when using an install profile that does not set a default theme. See https://www.drupal.org/node/3083055, which includes recommendations on which theme to use.

There is one of these warnings for each of the 10 tests in \Functional\TypedDataFormWidget

Change record https://www.drupal.org/node/3083055

I will work on a patch for this.

Comments

jonathan1055 created an issue. See original summary.

tr’s picture

For this I think we should add an abstract test base which can be used by all the TypedDataFormWidget tests defined by this module and also by any tests of form widget plugins defined in contrib. All form widget plugins need to test the same things, testIsApplicable(), testFormEditing(), and testValidation() at a minimum, so let's do what we can in the base class setup (including defaultTheme) and define these abstract functions to force subclasses to implement these tests. Subclasses can of course add additional tests, but we want to ensure that they all test at least these basics.

jonathan1055’s picture

Status: Active » Needs review
StatusFileSize
new3.16 KB

OK, yes. Something like this (only done for TextInputWidgetTest.php in this patch). How were you visualising forcing the actual tests to implement those three standard tests? I have defined them in the base class and set a guaranteed failure assertion.

What's the best way to add Drupal\Core\TypedData\DataDefinition into the base class so that each of the actual test classes do not have to add it? If I add it, it fails phpcs saying 'unused use statement'

tr’s picture

It's an abstract base class, so just define the functions with no implementations, like this: abstract public function testFormEditing();

This forces subclasses to provide an implementation, but can't force the subclasses to do anything reasonable. However, future widgets and contributed widgets will almost certainly use the the existing ones as a pattern, in which case it will be obvious what is needed in these test functions. Adding a few sentences to the function documentation explaining what they are for wouldn't hurt either ...

Declaring a use in the base class will have no effect on the subclasses. What can be done is maybe define a helper function on the base class. So instead of all the subclasses having to do something like $this->assertFalse($this->widget->isApplicable(DataDefinition::create('boolean')));, maybe add a protected function assertIsApplicable($widget, $type) function on the base class (probably also need assertIsNotApplicable()) then the subclasses don't need to deal with DataDefinition at all, they would just substitute that old call for $this->assertIsNotApplicable($this->widget, 'boolean');

The whole point here is not to create extra work, it's to gather some common functionality so as to reduce the effort it takes to write tests for new widgets and also to eliminate code duplication so as to have more robust and maintainable code. Whenever I see a "fix" that needs to be made in half a dozen different classes, then notice all those classes are essentially doing identical tasks with very few differences, that makes me think there's an opportunity for improvement.

jonathan1055’s picture

StatusFileSize
new8.74 KB

OK, thanks for the info. Yes that helper function would be good and I will raise a separate issue for that, which I can work on once this issue is fixed.

Whenever I see a "fix" that needs to be made in half a dozen different classes ...

Yes I absolutely agree, I always think that too. Reducing code duplication helps us all.

Regarding the abstract test class, phpunit just produces the abstruse and not particularly enlightening message

Fatal error: Class Drupal\Tests\typed_data\Functional\TypedDataFormWidget\BrokenWidgetTest contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Drupal\Tests\typed_data\Functional\TypedDataFormWidget\FormWidgetBrowserTestBase::testValidation) in /Library/webServer/web/dr-source/modules-D8-git/typed_data/tests/src/Functional/TypedDataFormWidget/BrokenWidgetTest.php on line 17

I do understand the message, but it is not very user-friendly. Implementing those three functions as public, with just a single code line

  public function testValidation() {
    $this->fail('There is no implementation of the mandatory test function: ' . $this->getName());
  }

gives a much more immediately useful message

There was 1 failure:
1) Drupal\Tests\typed_data\Functional\TypedDataFormWidget\BrokenWidgetTest::testValidation
There is no implementation of the mandatory test function: testValidation

I have added a $widgetId protected property to each test class, which then allows the creation of the widget to be done in the Base class setUp() instead

This patch will fail because two widget tests do not implement all of the three mandatory test functions.

jonathan1055’s picture

StatusFileSize
new11.49 KB
new3.39 KB

Updated patch with those two missing testValidation functions implemented. For TextareaWidgetTest I wrote a proper test using a constraint on length, minimum 40 chars. For BrokenWidgetTest there is a just an empty implementation as there is no validation possible (I think?).

tr’s picture

gives a much more immediately useful message

OK, that is more helpful.

I have added a $widgetId protected property to each test class, which then allows the creation of the widget to be done in the Base class setUp() instead

I don't really like this - $widgetId is only defined in the subclasses yet it is required/used in the base class setUp(). And how do the subclasses know they have to define $widgetId? Seems pretty fragile.

I suggest instead define the base class setUp() function to accept $widgetId as an argument. This base class functionsetUp($widgetId) should call parent::setUp() and also store the $widgetId as a base class property. Then the subclasses will need to declare their own setUp() functions which call parent::setUp($widgetId). This is a pattern also seen in core, for example views/tests/src/Functional/ViewTestBase.php.

For BrokenWidgetTest there is a just an empty implementation as there is no validation possible (I think?)

That sounds reasonable. But doesn't that trigger a 'risky test' warning?

jonathan1055’s picture

StatusFileSize
new11.49 KB
new3.83 KB

Thanks for the feedback.

... define the base class setUp() function to accept $widgetId as an argument.

I tried that. In the base class protected function setUp(string $widgetId) { gives:

Fatal error: Uncaught Declaration of Drupal\Tests\typed_data\Functional\TypedDataFormWidget\FormWidgetBrowserTestBase::setUp(string $widgetId) should be compatible with Drupal\Tests\BrowserTestBase::setUp()

Hence I removed the parameter definition, and extract it via func_get_arg(). If the parameter is not specified the default error message is
func_get_arg(): Argument 0 not passed to function which is not very friendly, so I have made this more helpful by failig the test with:

Function parent::setUp() must be called with a widget id parameter

I used the condensed syntax

    func_num_args() == 1 ? $this->widgetId = func_get_arg(0)
      : $this->fail('Function parent::setUp() must be called with a widget id parameter');

but let me know if you prefer it written out with a full if .. then .. else

But doesn't that trigger a 'risky test' warning?

No oddly it seems to be fine. My local testing and on Travis are clean, no warnings.

tr’s picture

Yeah, in order for this to work the new argument must have a default value, so protected function setUp($widgetId = 'broken') would work, with the id defaulting to 'broken' if it wasn't provided by the subclass. Also, typehinting with 'string' is not allowed in D8.7. See #3038168: [meta] Add scalar type hinting and return value typing.

jonathan1055’s picture

StatusFileSize
new11.72 KB
new3.83 KB

I can't make it work just with the base class protected function setUp($widgetId = 'broken'). That produces the similar error but the other way round:

Fatal error: Uncaught Declaration of Drupal\Tests\typed_data\Functional\TypedDataFormWidget\BrokenWidgetTest::setUp() should be compatible with Drupal\Tests\typed_data\Functional\TypedDataFormWidget\FormWidgetBrowserTestBase::setUp($widgetId = 'broken')

i.e. the sub-class definition protected function setUp() does not match the base class. So now the calling functions have to changed to look like

  protected function setUp($widgetId = 'select') {
    parent::setUp($widgetId);
  }

I tested calling without a parameter so that the widget defaulted to 'broken' but this was very confusing. There was nothing to show what was wrong, just an unexpected set of test failures, e.g. the isApplicable result was wrong. It could be frustrating to try and fix tests when actually you are inadvertantly testing against the wrong widget. So I have defaulted to blank, and force the same helpful test failure message as before.

The problem now is that we get coding standards warnings for each of the sub-class functions:

FILE: ...ta/tests/src/Functional/TypedDataFormWidget/SelectWidgetTest.php
----------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
----------------------------------------------------------------------
 33 | WARNING | Possible useless method overriding detected
    |         | (Generic.CodeAnalysis.UselessOverridingMethod.Found)
----------------------------------------------------------------------

Patch attached, but clearly this still needs some change.

jonathan1055’s picture

Status: Needs review » Needs work

I have created #3110650: Move creation of $widget into WidgetBrowserTestBase and postponed it on this issue. The setUp() functions will remain as-is, so that I can get back to scope and fix the $defaultTheme.

jonathan1055’s picture

Status: Needs work » Needs review
StatusFileSize
new9.18 KB

Back to a simple version of the patch, to create the new FormWidgetBrowserTestBase and define $defaultTheme

jonathan1055’s picture

StatusFileSize
new9.59 KB
new1.5 KB

I have realised that the $modules property should be 'protected' not 'public' so might as well make that correction now. Also the sub-classes only have to define additional modules that are extra to those set in the base class.

  • TR committed 338cc75 on 8.x-1.x authored by jonathan1055
    Issue #3109923 by jonathan1055: Deprecation: add $defaultTheme to...
tr’s picture

Status: Needs review » Fixed

Took me a while, but I finally had a chance to look at this again.

Committed. Thanks.

jonathan1055’s picture

Assigned: jonathan1055 » Unassigned

Thank you. Unassigning.

Status: Fixed » Closed (fixed)

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

jonathan1055’s picture

Title: Deprecation: add $defaultTheme to browser tests » Deprecation: add $defaultTheme - create FormWidgetBrowserTestBase class

Expanded title as this issue creates tests/src/Functional/TypedDataFormWidget/FormWidgetBrowserTestBase.php

Also I have unpostponed #3110650: Move creation of $widget into WidgetBrowserTestBase