Problem/Motivation

In PHPUnit tests, we can use assertEquals() to compare MarkupInterface objects via the MarkupInterfaceComparator. Often, we only care about the text of the markup, and not the specific HTML tags.

Proposed resolution

This issue changes MarkupInterfaceComparator so it strips tags before comparing; this way only the text of the markup is considered for equality.

To provide a backward compatibility safety net, if both the expected and actual values contain markup (that is, the test is sensitive to the full markup and not just the text), a deprecation is issued informing the user that they should explicitly cast to string and use assertSame() instead.

Remaining tasks

User interface changes

None

API changes

In tests, assertEquals() will issue a deprecation if both the expected and actual values contain markup. Tests that are markup-sensitive should use assertSame() instead.

Data model changes

None

Release notes snippet

CommentFileSizeAuthor
#80 3153468-76.patch73 KBmondrake
#80 interdiff_73-76.txt993 bytesmondrake
#73 3153468-73.patch73.09 KBmondrake
#73 interdiff_72-73.txt4.95 KBmondrake
#72 3153468-72.patch73.09 KBmondrake
#72 interdiff_71-72.txt7.36 KBmondrake
#72 interdiff_71-72.txt73.09 KBmondrake
#71 interdiff_70-71.txt53.52 KBmondrake
#71 3153468-71.patch66.36 KBmondrake
#70 interdiff_69-70.txt631 bytesmondrake
#70 3153468-70.patch17.96 KBmondrake
#69 interdiff_63-69.txt10.16 KBmondrake
#69 3153468-69.patch17.68 KBmondrake
#63 interdiff_61-63.txt4.74 KBmondrake
#63 3153468-63.patch9.62 KBmondrake
#61 3153468-61-test-only.patch4.89 KBmondrake
#59 3153468-59-test-only.patch4.78 KBmondrake
#57 3153468-57-test-only.patch4.13 KBmondrake
#54 3153468-54-test-only.patch2.4 KBmondrake
#46 3153468-46.patch26.5 KBsmustgrave
#46 interdiff-44-46.txt3.06 KBsmustgrave
#44 3153468-44.patch29.63 KBsmustgrave
#44 interdiff-mr-44.txt9.37 KBsmustgrave
#39 Screenshot from 2021-10-12 17-57-43.png121.49 KBvikashsoni
#26 interdiff-24-26.txt1.6 KBnaresh_bavaskar
#26 3153468-26.patch71.19 KBnaresh_bavaskar
#23 interdiff_22-23.txt2.09 KBmohrerao
#23 3153468-23.patch69.42 KBmohrerao
#22 interdiff_20-22.txt993 bytesmohrerao
#22 3153468-22.patch69.24 KBmohrerao
#20 interdiff_17-20.txt12.06 KBmohrerao
#20 3153468-20.patch69.22 KBmohrerao
#17 interdiff_16-17.txt1.06 KBmohrerao
#17 3153468-17.patch57.15 KBmohrerao
#15 interdiff_12-16.txt32.48 KBmohrerao
#15 3153468-16.patch57.14 KBmohrerao
#14 interdiff_12-14.txt2.23 KBravi.shankar
#14 3153468-14.patch57.13 KBravi.shankar
#12 interdiff_9-12.txt26.43 KBmohrerao
#12 3153468-12.patch57.14 KBmohrerao
#10 3153468-9.patch55.72 KBhash6
#10 interdiff_5-9.txt19.11 KBhash6
#5 interdiff_2-5.txt1.02 KBmeena.bisht
#5 3153468-5.patch56.22 KBmeena.bisht
#2 3153468-2.patch56.22 KBhardik_patel_12

Issue fork drupal-3153468

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Hardik_Patel_12 created an issue. See original summary.

hardik_patel_12’s picture

Status: Active » Needs review
StatusFileSize
new56.22 KB

Kindly review the patch.

meena.bisht’s picture

Assigned: Unassigned » meena.bisht
meena.bisht’s picture

Status: Needs review » Needs work
meena.bisht’s picture

StatusFileSize
new56.22 KB
new1.02 KB

Please find the below patch with the required changes.

meena.bisht’s picture

Assigned: meena.bisht » Unassigned
Status: Needs work » Needs review
hash6’s picture

Assigned: Unassigned » hash6

Status: Needs review » Needs work

The last submitted patch, 5: 3153468-5.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

naresh_bavaskar’s picture

I think let's wait for https://www.drupal.org/project/drupal/issues/3131281 to be fixed then we should remove t() from assertEquals

hash6’s picture

Related issues: +#3131281: Replace assertEqual() with assertEquals()
StatusFileSize
new19.11 KB
new55.72 KB

@meena.bisht Thanks for the patch.
- Updated the patch with fixes for third argument in assertEqual().
@naresh_bavaskar
- As mentioned by Naresh, have removed just the t() from assertEqual() and other patch would update it to assertEquals()
If needed will re roll the patch once https://www.drupal.org/project/drupal/issues/3131281 is marked as fixed and commited.

hash6’s picture

Assigned: hash6 » Unassigned
Status: Needs work » Needs review
mohrerao’s picture

StatusFileSize
new57.14 KB
new26.43 KB

Fixed failing tests and missed removal of t() in assertTrue()

longwave’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/system/tests/src/Functional/Form/ElementsLabelsTest.php
    @@ -91,9 +91,9 @@ public function testFormLabels() {
    +    $this->assertEqual($elements[0]->getAttribute('title'), 'Checkboxes test' . ' (Required)', 'Title attribute found.');
    ...
    +    $this->assertEqual($elements[0]->getAttribute('title'), 'Radios test' . ' (Required)', 'Title attribute found.');
    

    The two strings here can be joined together rather than using the concatenation operator.

  2. +++ b/core/modules/user/tests/src/Kernel/UserValidationTest.php
    @@ -125,21 +125,21 @@ public function testValidation() {
    +    $this->assertEqual(strip_tags($violations[0]->getMessage()), "{$user->getFieldDefinition('mail')->getLabel()} field is required.");
    

    Why are we using string interpolation here when in other places we use concatenation?

ravi.shankar’s picture

Status: Needs work » Needs review
StatusFileSize
new57.13 KB
new2.23 KB

Here I have tried to address comment #13.

mohrerao’s picture

StatusFileSize
new57.14 KB
new32.48 KB

@longwave,

  1. Fixed #13.1
  2. Used string interpolation for all replacements

Status: Needs review » Needs work

The last submitted patch, 15: 3153468-16.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mohrerao’s picture

StatusFileSize
new57.15 KB
new1.06 KB

Fixed failing test in #15

mohrerao’s picture

Status: Needs work » Needs review
longwave’s picture

Status: Needs review » Needs work

Needs reroll, and a few places seem to have been missed:

core/modules/content_translation/tests/src/Functional/ContentTranslationUITestBase.php
core/modules/views/tests/src/Functional/Plugin/DisplayTest.php
core/modules/file/tests/src/Kernel/FileManagedUnitTestBase.php
core/modules/file/tests/src/Functional/FileManagedTestBase.php

mohrerao’s picture

Status: Needs work » Needs review
StatusFileSize
new69.22 KB
new12.06 KB

Thanks for the review @longwave
Rerolled #17 and fixed missed files core/modules/content_translation/tests/src/Functional/ContentTranslationUITestBase.php
core/modules/views/tests/src/Functional/Plugin/DisplayTest.php
core/modules/file/tests/src/Kernel/FileManagedUnitTestBase.php
core/modules/file/tests/src/Functional/FileManagedTestBase.php

Status: Needs review » Needs work

The last submitted patch, 20: 3153468-20.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mohrerao’s picture

Status: Needs work » Needs review
StatusFileSize
new69.24 KB
new993 bytes

Fixed failing test in #20

mohrerao’s picture

StatusFileSize
new69.42 KB
new2.09 KB

Fixed CS issues in #22

longwave’s picture

Status: Needs review » Needs work

This looks almost there, I found two more cases:

core/modules/views/tests/src/Kernel/Handler/FilterCombineTest.php
242:    $this->assertEquals(t('Field %field set in %filter is not set in display %display.', ['%field' => 'dummy', '%filter' => 'Global: Combine fields filter', '%display' => 'Master']), reset($errors['default']));
277:    $this->assertEquals(t('%display: %filter can only be used on displays that use fields. Set the style or row format for that display to one using fields to use the combine field filter.', ['%filter' => 'Global: Combine fields filter', '%display' => 'Master']), reset($errors['default']));
naresh_bavaskar’s picture

Assigned: Unassigned » naresh_bavaskar
naresh_bavaskar’s picture

Assigned: naresh_bavaskar » Unassigned
Status: Needs work » Needs review
StatusFileSize
new71.19 KB
new1.6 KB

addressed the #24 changes. Please review

longwave’s picture

Looking at the amount of calls to strip_tags() added here, wondering if we should either include the raw HTML in the expected string, or add a helper like assertTextEquals() which calls strip_tags() on the actual value?

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

longwave’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

If #27 is making this too complicated we could split this out into two issues, one for t() that don't use % arguments (as these cases should be simple) and then handle the ones that do use % arguments separately?

mondrake’s picture

This badly needs a reroll now, assertEqual() is gone.

+1 on #30, let's do first t() with no args where t() is one of the two compared variables, plus I'd say all the t() that are in the $message - these can be simply removed sometimes. The rest we can address later.

mondrake’s picture

+++ b/core/modules/aggregator/tests/src/Kernel/FeedValidationTest.php
@@ -55,13 +55,9 @@ public function testValidation() {
-    $this->assertEqual($violations[0]->getMessage(), t('A feed named %value already exists. Enter a unique title.', [
-      '%value' => $feed->label(),
-    ]));

BTW things like these could IMO just stay as-is, because $violations[] will be probably an array of t() objects built by the runtime code.

longwave’s picture

Status: Needs work » Postponed
Issue tags: -Needs reroll

Opened #3226008: Remove simple uses of t() in assertEquals() calls for #31, postponing this on that.

longwave’s picture

Title: Remove uses of t() in assertEqual() and assertEquals() calls » Remove uses of t() in assertEquals() calls
Status: Postponed » Needs work

#33 is in, this just leaves about 75 cases that mostly use t('%something') or are actual multilingual tests of t(). Maybe we just do nothing with these remaining ones?

Regex for searching; assertEquals.*\Wt\(

mallezie made their first commit to this issue’s fork.

mallezie’s picture

Found 66 instances remaining. 23 of them were testing the t() function so those are not adjusted.
The other ones are adjusted. I compared values with the raw html instead of the strip_tags apporach, since the last one loses some of the assert testing (does not spot changed tags offcourse).

mallezie’s picture

Status: Needs work » Needs review
vikashsoni’s picture

StatusFileSize
new121.49 KB

I test the patch but patch not working in 9.3.x-dev
Patch needs to re-roll

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Status: Needs review » Needs work

MR needs to be updated to 10.1

smustgrave’s picture

Status: Needs work » Needs review
StatusFileSize
new9.37 KB
new29.63 KB

For some reason couldn't create a branch off 10.1.x (says it doesn't exist)

So uploading a patch for 10.1

Status: Needs review » Needs work

The last submitted patch, 44: 3153468-44.patch, failed testing. View results

smustgrave’s picture

Status: Needs work » Needs review
StatusFileSize
new3.06 KB
new26.5 KB

Changed some back as they were doing variable replacements.

Status: Needs review » Needs work

The last submitted patch, 46: 3153468-46.patch, failed testing. View results

smustgrave’s picture

Status: Needs work » Needs review

Think it's a random layout builder javascript failure.

longwave’s picture

Status: Needs review » Reviewed & tested by the community

Sent it for a retest, this all looks OK to me now, assuming it comes back green this time. After sitting on it for a while I think checking the full explicit string is better than using strip_tags(), we are unlikely to ever change the placeholder HTML and if we consider it then the more test fails that it catches the better.

mondrake’s picture

If at least one of the expected or the actual value are a MarkupInterface object, our MarkupInterfaceComparator kicks in to evaluate the assertEquals. I wonder whether we should strip tags in the comparator instead.

longwave’s picture

I think that's a dangerous assumption to make in the comparator, we probably want to explicitly test full markup in most cases? Tests can and should be specific about what they are testing, and I think adding "magic" in comparisons leads to difficulty later in understanding and maintaining the tests.

mondrake’s picture

I see your point @longwave, but we already went half way with magic and assumptions introducing our own comparator that resolves MarkupInterface objects to strings.

It all goes back to the intent of each test assertion IMHO. If the intent is to check that two texts are equal, then we should not care of HTML tags that are added in. That was the intent covered by the introduction of the comparator, and it would make sense to me to enhance the comparator to strip tags before comparing. So that we have something like:

+++ b/core/modules/comment/tests/src/Kernel/CommentValidationTest.php
@@ -101,7 +101,7 @@ public function testValidation() {
-    $this->assertEquals(t('The name you used (%name) belongs to a registered user.', ['%name' => 'test']), $violations[0]->getMessage());
+    $this->assertEquals('The name you used (test) belongs to a registered user.', $violations[0]->getMessage());

Viceversa, if the intent is to verify the actual markup, we should be stricter: use assertSame() and cast explicitly to string the actual MarkupInterface object:

+++ b/core/modules/comment/tests/src/Kernel/CommentValidationTest.php
@@ -101,7 +101,7 @@ public function testValidation() {
-    $this->assertEquals(t('The name you used (%name) belongs to a registered user.', ['%name' => 'test']), $violations[0]->getMessage());
+    $this->assertSame('The name you used (<em class="placeholder">test</em>) belongs to a registered user.', (string) $violations[0]->getMessage());
longwave’s picture

Yeah, it comes down to what "equals" means when markup is involved, and that is not clearly defined either way. However I think that stripping tags in the comparator would be a BC break? Maybe worth a test patch to see how much is broken? Or, as suggested in #27, we could add something like assertTextEquals() to strip tags first?

mondrake’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new2.4 KB

Here's a test patch. Let's see.

longwave’s picture

The issue with this is that we might just be comparing two empty strings in some cases now, which invalidates the test? Should we add an extra check for this?

Status: Needs review » Needs work

The last submitted patch, 54: 3153468-54-test-only.patch, failed testing. View results

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new4.13 KB

@longwave not sure I grasp #55, can you please elaborate?

longwave’s picture

Imaginary test case:

/** @var \Drupal\Component\Render\MarkupInterface $html */
$html = $some_object->outputHtml();
$this->assertEquals('<div class="test"><img src="test.jpg" /></div>', $html);

With strip_tags() in the comparator, this is now just comparing two empty strings, so if we later break outputHtml() in some way, the test will still pass as a false positive?

Again this is really just defining what "equals" means in terms of markup, but I don't think we can assume that all existing cases should consider only the text from the markup.

We could possibly try calling strip_tags() on $actual, only if $expected contains no tags? Or only if it is a MarkupInterface object? But that seems like even more "magic" behaviour.

mondrake’s picture

StatusFileSize
new4.78 KB

So the problem would be when BOTH expected and actual values contain HTML tags and we may be expecting them to be significant in the comparison. Let's try this. Note that if we now compare with stripped values, assertEquals will test successfully when differences are in the markup tags only (different tags will no longer differentiate), so we leave to the deprecation to notify the need to tighten the test.

Status: Needs review » Needs work

The last submitted patch, 59: 3153468-59-test-only.patch, failed testing. View results

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new4.89 KB

If BOTH expected and actual are MarkupInterface though, we should not flag it... no need to test MarkupInterface itself here. So this only applies between a string and a MarkupInterface object.

Status: Needs review » Needs work

The last submitted patch, 61: 3153468-61-test-only.patch, failed testing. View results

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new9.62 KB
new4.74 KB

I tried to fix some of the test failures and, look, I start liking the concept a lot...

Status: Needs review » Needs work

The last submitted patch, 63: 3153468-63.patch, failed testing. View results

mondrake’s picture

I am pausing now. Waiting for feedback.

longwave’s picture

I like it! This simplifies the more basic assertions but forces us to explicitly say in the test whether markup is significant or not in more complex cases. It would also be good to add extra cases to MarkupInterfaceComparatorTest to cover this change.

mondrake’s picture

Assigned: Unassigned » mondrake

Will do. Thanks!

mondrake’s picture

mondrake’s picture

StatusFileSize
new17.68 KB
new10.16 KB
mondrake’s picture

StatusFileSize
new17.96 KB
new631 bytes
mondrake’s picture

StatusFileSize
new66.36 KB
new53.52 KB
mondrake’s picture

Assigned: mondrake » Unassigned
Status: Needs work » Needs review
StatusFileSize
new73.09 KB
new7.36 KB
new73.09 KB

Added tests extra cases to MarkupInterfaceComparatorTest.

mondrake’s picture

StatusFileSize
new4.95 KB
new73.09 KB

spell fixes

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Needs Review Queue Initiative

Changes look good.

longwave’s picture

Looks great - one nitpick that could go either way:

+++ b/core/modules/field/tests/src/Kernel/EntityReference/Views/SelectionTest.php
@@ -132,7 +133,10 @@ public function testSelectionHandler() {
+      $subresults_formatted = array_map(function (MarkupInterface $markup): string {
+        return (string) $markup;
+      }, $subresults);
+      $filtered_rendered_results_formatted += $subresults_formatted;

I think it's somewhat less verbose to just use strval()?

$filtered_rendered_results_formatted += array_map('strval', $subresults_formatted);

or if you want to be more strictly typed

$filtered_rendered_results_formatted += array_map(fn(MarkupInterface $markup) => (string) $markup, $subresults_formatted);
longwave’s picture

Status: Reviewed & tested by the community » Needs work

...so while this solves the issue of assertEquals() comparing markup, it no longer solves the actual issue of removing t() in assertEquals() - there are a whole bunch of cases left:

$ rg 'assertEquals.*\Wt\('|sort
core/modules/contact/tests/src/Functional/ContactSitewideTest.php:    $this->assertEquals(t('[@label] @subject', ['@label' => $label, '@subject' => $edit['subject[0][value]']]), $mail['subject']);
core/modules/field/tests/src/Kernel/FieldCrudTest.php:    $this->assertEquals(t('%name does not accept the value @value.', ['%name' => $field_name, '@value' => -2]), $violations[0]->getMessage());
core/modules/field/tests/src/Kernel/FieldCrudTest.php:    $this->assertEquals(t('This value should be between %min and %max.', ['%min' => 0, '%max' => 32]), $violations[0]->getMessage());
core/modules/field/tests/src/Kernel/FieldCrudTest.php:    $this->assertEquals(t('This value should be between %min and %max.', ['%min' => 0, '%max' => 32]), $violations[1]->getMessage());
core/modules/field/tests/src/Kernel/FieldValidationTest.php:    $this->assertEquals(t('%name: this field cannot hold more than @count values.', ['%name' => $this->fieldTestData->field->getLabel(), '@count' => $cardinality]), $violations[0]->getMessage());
core/modules/file/tests/src/Kernel/SaveTest.php:    $this->assertEquals(t('The file %value already exists. Enter a unique file URI.', ['%value' => $uppercase_file_duplicate->getFileUri()]), $violations[0]->getMessage());
core/modules/forum/tests/src/Kernel/ForumValidationTest.php:    $this->assertEquals(t('The item %forum is a forum container, not a forum. Select one of the forums below instead.', ['%forum' => $container->label()]), $violations[0]->getMessage());
...
xjm’s picture

So the title and IS don't seem to be describing what's actually being done in this issue.

The scope of the parent meta was intended to be removing unnecessary direct uses of t() and friends in test fixture data. However, this issue's primary scope seems to be that of converting assertEquals() calls to assertSame() (and therefore adding a string cast on passed-in markup objects, where t() is already not called directly in the assertions).

If the goal is to remove use of assertEquals() with translatable strings and markup objects, then the IS should be completely rewritten and the issue retitled.

Thanks for working on this!

smustgrave’s picture

With such a large number of changes would it make sense to breakup?

longwave’s picture

I don't think it can be broken up easily but we might want to spin off a child issue for the changes to MarkupInterfaceComparator and then come back here to fix the assertEquals() calls.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new993 bytes
new73 KB

Addressed #75.

xjm’s picture

@smustgrave, actually this issue is about the perfect size for a mostly 1:1 replacement on a single line that requires some limited thinking about each change. Edit: There is relevant text in the issue scope policy section on patch side.

Large patches with many instances of the same simple change can still be easy to create and review, while even small patches with complex new code or documentation can take longer. Good conceptual scope is more important than the size alone.

FWIW re: #77, I chatted with @longwave about this. The current scope is fine if we update the title and IS, but we would need a followup for the original scope?

mondrake’s picture

Status: Needs review » Needs work

#80 xposted with #76, reset status to NW.

longwave’s picture

Title: Remove uses of t() in assertEquals() calls » Strip HTML tags when using assertEquals() to compare markup
Issue summary: View changes
Issue tags: -Needs issue summary update +Needs change record updates

Opened #3337295: Remove remaining uses of t() in assertEquals() calls to handle the remainder, and retitled and updated the IS of this issue.

The change record still needs work.

mondrake’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record updates

Fleshed the CR.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Reviewed the change record and looks great. The examples were super helpful

longwave’s picture

RTBC +1, the change record is nice and clear to me.

edit: I didn't work on this directly so could actually commit it, but would like another core committer opinion on the changes first.

catch’s picture

+1 from me.

  • longwave committed c187cc63 on 10.1.x
    Issue #3153468 by mondrake, mohrerao, mallezie, smustgrave, meena.bisht...
longwave’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed c187cc6339 to 10.1.x. Thanks!

Status: Fixed » Closed (fixed)

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