Problem/Motivation

As title, we can explore replacing assertions looking at results of the drupalGetHeader method, by asserting the session and then using methods from WebAssert on it.

Proposed resolution

For example:

-    $this->assertEqual($expectedGeneratorHeader, $this->drupalGetHeader('X-Generator'));
+    $this->assertSession()->responseHeaderEquals('X-Generator', $expectedGeneratorHeader);

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Comments

mondrake created an issue. See original summary.

mondrake’s picture

Assigned: Unassigned » mondrake

Started working on this.

longwave’s picture

Perhaps we can even deprecate drupalGetHeader() after this?

mondrake’s picture

Status: Active » Needs work
StatusFileSize
new30.62 KB

First iteration

mondrake’s picture

StatusFileSize
new84.64 KB
mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new84.66 KB

This more or less should do, up for review now. I only replaced the occurences of drupalGetHeader where it's inlined in standard assertions. There remain a few cases where drupalGetHeader is called and its value associated to a variable that is then used for further assertions or other cases. In general we can think of replacing all those with $this->getSession()->getResponseHeader($name);, and deprecate drupalGetHeader, but I'd leave that to a follow-up since I think that deserves specific attention on how to convert.

Status: Needs review » Needs work

The last submitted patch, 6: 3131186-6.patch, failed testing. View results

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new84.24 KB
new1.04 KB

Fixing failure at #6

mondrake’s picture

Assigned: mondrake » Unassigned
longwave’s picture

Status: Needs review » Reviewed & tested by the community

Conversions here all look good. I agree with pushing the rest to a followup to keep this one simple.

jungle’s picture

+++ b/core/modules/page_cache/tests/src/Functional/PageCacheTest.php
@@ -246,44 +246,44 @@ public function testPageCache() {
+    $this->assertNull($this->getSession()->getResponseHeader('X-Drupal-Cache'));
+    $this->assertSession()->responseHeaderNotContains('Vary', 'cookie');
+    $this->assertSession()->responseHeaderEquals('Cache-Control', 'must-revalidate, no-cache, private');
+    $this->assertSession()->responseHeaderEquals('Expires', 'Sun, 19 Nov 1978 05:00:00 GMT');
+    $this->assertSession()->responseHeaderEquals('Foo', 'bar');

For lines like this, is it necessary to introduce a variable for $this->assertSession()? It gets called many times.

jungle’s picture

Continue with #11, if we are going to doing so, I think it should be done in a separate issue. As it does not just happen here.

mondrake’s picture

#12 it's a good point. When we'll start removing legacy assertions following #3114617: Properly deprecate methods in Drupal\FunctionalTests\AssertLegacyTrait and Drupal\KernelTests\AssertLegacyTrait, while still keeping deprecations silenced, there will be tons of these.

But I think that will have to be addressed afterwards.

Also - now a WebAssert failure leads to an exception, and an Error (not a Failure) reported by PHPUnit. That's because in our implementation we do not catch Exceptions thrown by WebAssert. We may think about a new set of methods, included in a trait, used by FunctionalTests, that will absorb the Exception and report a Failure (with a context message, currently missing).

Kinda, instead of

$this->assertSession()->responseHeaderNotContains('Vary', 'cookie');

rather

$this->assertSessionResponseHeaderNotContains('Vary', 'cookie', 'my bold failure message');

longwave’s picture

+++ b/core/modules/basic_auth/tests/src/Functional/BasicAuthTest.php
@@ -46,7 +45,7 @@ public function testBasicAuth() {
-    $this->assertNull($this->drupalGetHeader('X-Drupal-Cache'));
+    $this->assertNull($this->getSession()->getResponseHeader('X-Drupal-Cache'));

Looked at the code behind this and I think we might be able to use this instead of assertNull?

$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', NULL);
longwave’s picture

Status: Reviewed & tested by the community » Needs work

NW for #14, apologies for not doing this before - set back to RTBC if you disagree!

mondrake’s picture

Status: Needs work » Reviewed & tested by the community

No worries. I disagree, though: I was pondering about this the same way, but then IMHO what we want in cases like this is to check that the header is missing from the response, but in your suggestion it may exist but have a null value. What's missing I think is a responseHeaderExists method upstream in WebAssert.

In Mink, getResponseHeader returns null when the header is missing, so that's more accurate IMHO.

    /**
     * Returns specific response header.
     *
     * @param string $name
     *
     * @return string|null
     */
    public function getResponseHeader($name)
    {
        $headers = $this->driver->getResponseHeaders();

        $name = strtolower($name);
        $headers = array_change_key_case($headers, CASE_LOWER);

        if (!isset($headers[$name])) {
            return null;
        }

        return is_array($headers[$name]) ? $headers[$name][0] : $headers[$name];
    }
longwave’s picture

And responseHeaderEquals() checks that return value using === so $actual NULL will only check a NULL response and not pass for e.g. the empty string.

        $actual = $this->session->getResponseHeader($name);
        $message = sprintf('Current response header "%s" is "%s", but "%s" expected.', $name, $actual, $value);

        $this->assert($value === $actual, $message);

Wouldn't getResponseHeader() also return NULL if the header value was NULL instead of missing? Can a header value even be NULL, isn't it always a string or not set?

mondrake’s picture

Can a header value even be NULL, isn't it always a string or not set?

I guess you'd be right, still I think this would be confusing. I think it'd be better file a PR upstream to get a clear existence checking method. I see there's another similar pending https://github.com/minkphp/Mink/pull/630

mondrake’s picture

xjm’s picture

Assigned: Unassigned » xjm

Reviewing.

xjm’s picture

Assigned: xjm » Unassigned
Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/basic_auth/tests/src/Functional/BasicAuthTest.php
    @@ -73,9 +72,9 @@ public function testBasicAuth() {
    -    $this->assertNull($this->drupalGetHeader('X-Drupal-Cache'));
    +    $this->assertNull($this->getSession()->getResponseHeader('X-Drupal-Cache'));
    

    Why are we retaining assertNull() here as the assertion? ... I see #14 - #18 are about this.

  2. +++ b/core/modules/config/tests/src/Functional/ConfigExportUITest.php
    @@ -58,9 +58,7 @@ public function testExport() {
    -    $header_match = (boolean) preg_match('/attachment; filename="config-' . preg_quote($hostname) . '-\d{4}-\d{2}-\d{2}-\d{2}-\d{2}\.tar\.gz"/', $header_content_disposition);
    ...
    +    $this->assertSession()->responseHeaderMatches('content-disposition', '/attachment; filename="config-' . preg_quote($hostname) . '-\d{4}-\d{2}-\d{2}-\d{2}-\d{2}\.tar\.gz"/');
    

    Nice one.

  3. +++ b/core/modules/dynamic_page_cache/tests/src/Functional/DynamicPageCacheIntegrationTest.php
    @@ -56,23 +56,23 @@ public function testDynamicPageCache() {
    -    $this->assertNull($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response object returned: Dynamic Page Cache is ignoring.');
    +    $this->assertNull($this->getSession()->getResponseHeader(DynamicPageCacheSubscriber::HEADER));
    
    @@ -93,39 +93,39 @@ public function testDynamicPageCache() {
    -    $this->assertNull($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, rendered as HTML response, admin route: Dynamic Page Cache is ignoring');
    ...
    -    $this->assertNull($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, plain response, admin route: Dynamic Page Cache is ignoring');
    ...
    -    $this->assertNull($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, cacheable response, admin route: Dynamic Page Cache is ignoring');
    ...
    -    $this->assertEqual('UNCACHEABLE', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response, but uncacheable: Dynamic Page Cache is running, but not caching.');
    ...
    -    $this->assertEqual('UNCACHEABLE', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response, but uncacheable: Dynamic Page Cache is running, but not caching.');
    ...
    -    $this->assertEqual('MISS', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'By default, Drupal has no auto-placeholdering cache tags.');
    

    This assertion (and the rest in this test) is adding debugging information about the type of the response (Response object, cacheable response object, render array as HTML, render array as AJAX, dialog, etc.). Since there's many similar assertions in the test, it is making debugging a little harder to go back to the exact line number, but it's manageable. In most cases in the test, the inline comments already include the needed information. However, there are a few where the information isn't included directly in the inline comments. I've highlighted those here. In most cases, these are giving additional information about the fixture being tested at that point.

  4. +++ b/core/modules/file/tests/src/Functional/DownloadTest.php
    @@ -75,8 +75,8 @@ protected function doPrivateFileTransferTest() {
    -    $this->assertEqual($this->drupalGetHeader('x-foo'), 'Bar', 'Found header set by file_test module on private download.');
    -    $this->assertNull($this->drupalGetHeader('x-drupal-cache'), 'Page cache is disabled on private file download.');
    

    Let's put this in the inline comments as well.

I got about halfway through the patch. In terms of scope management, I'm wondering if it might make sense to separate assertions that already don't have assertion messages from those that do?

xjm’s picture

Issue tags: +Needs followup

I think we should file a followup for #14 - #18 perhaps.

mondrake’s picture

Assigned: Unassigned » mondrake

on this

mondrake’s picture

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

I investigated #14-#18 and filed #3133355: Introduce PHPUnit-compliant WebAssert::responseHeaderExists() method, and its negative, we do not necessarily need to get upstream involved. Postponing on that.

mondrake’s picture

Assigned: mondrake » Unassigned
mondrake’s picture

Status: Postponed » Needs work
Issue tags: +Deprecated assertions

Adding Deprecated assertion tag. Related issue was committed, can be worked on now.

mondrake’s picture

Assigned: Unassigned » mondrake

On this.

mondrake’s picture

StatusFileSize
new84.91 KB

Just a reroll for now.

mondrake’s picture

Assigned: mondrake » Unassigned
Status: Needs work » Needs review
StatusFileSize
new87.91 KB
new27.77 KB

Added calls to the new WebAssert::responseHeaderDoesNotExist method to replace $this->assertNull($this->getSession()->getResponseHeader('X-Drupal-Cache')); calls, and inlined back the $message parameters as comments where appropriate.

paulocs’s picture

Status: Needs review » Reviewed & tested by the community

Nice! Patch #30 looks good to me.
Set to RTBC.

May be we will need to re-roll patch from issue #3164589: Replace assertions that use $this->getSession()->getResponseHeader() with WebAssert methods after this issue is committed or the opposite. Both patches fixes the class BasicAuthTest.php. Let's see what will be committed first.

Cheers, Paulo.

mondrake’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll
longwave’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new86.43 KB

Rerolled.

longwave’s picture

StatusFileSize
new87.01 KB

Oops, missed a change from responseHeaderEquals(..., NULL) to responseHeaderDoesNotExist()

mondrake’s picture

Status: Needs review » Reviewed & tested by the community

thanks, back to rtbc

catch’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/dynamic_page_cache/tests/src/Functional/DynamicPageCacheIntegrationTest.php
@@ -56,23 +56,32 @@ public function testDynamicPageCache() {
     $url = Url::fromUri('route:dynamic_page_cache_test.html');
     $this->drupalGet($url);
-    $this->assertEqual('MISS', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response: Dynamic Page Cache is active, Dynamic Page Cache MISS.');
+    // Verify that 'render array is returned, rendered as HTML response:
+    // Dynamic Page Cache is active, Dynamic Page Cache MISS.

@@ -82,10 +91,14 @@ public function testDynamicPageCache() {
       $this->drupalGet($url);

Not sure if there is a script issue, but the opening single quote from the assertion message is being reproduced in the code comment (first time I read it I was looking for the closing single quote).

This happens several times in the patch.

I'm also not sure the comment is correct as written - we're not asserting anything about a render array, only the cache headers. So I think we could remove these comments since the assertion is fairly self-documenting.

Vidushi Mehta’s picture

Status: Needs work » Needs review
StatusFileSize
new86.36 KB
new2.32 KB

Changes are done mentioned by #36.

mondrake’s picture

Status: Needs review » Reviewed & tested by the community

There's a line space error

/var/lib/drupalci/workspace/jenkins-drupal_patches-58025/source/core/modules/dynamic_page_cache/tests/src/Functional/DynamicPageCacheIntegrationTest.php

line 79	Whitespace found at end of line

But that may be fixable on commit.

mondrake’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/dynamic_page_cache/tests/src/Functional/DynamicPageCacheIntegrationTest.php
@@ -56,23 +56,28 @@ public function testDynamicPageCache() {
+    // Verify that response object is returned: Dynamic Page Cache is ignoring.
...
+    // Verify that cacheable response object is returned: Dynamic Page Cache is active,
+    // Dynamic Page Cache MISS.
...
+    // Verify that cacheable response object is returned: Dynamic Page Cache is active,
+    // Dynamic Page Cache HIT.

@@ -93,39 +98,56 @@ public function testDynamicPageCache() {
+    // Verify that a Render array is returned, rendered as AJAX response:
+    // Dynamic Page Cache is ignoring.
...
+    // Verify that a Render array is returned, rendered as drupal_dialog response:
+    // Dynamic Page Cache is ignoring.
...
+    // Verify that a Render array is returned, rendered as modal response:
+    // Dynamic Page Cache is ignoring.
...
+    // Verify that a response is returned, rendered as HTML response, admin
+    // route: Dynamic Page Cache is ignoring.
...
+    // Verify that a response is returned, plain response, admin route: Dynamic
+    // Page Cache is ignoring.
...
+    // Verify that a response is returned, cacheable response, admin route:
+    // Dynamic Page Cache is ignoring.
...
+    // Verify that a render array is returned, rendered as HTML response,
+    // but uncacheable: Dynamic Page Cache is running, but not caching.
...
+    // Verify that a render array is returned, rendered as HTML response,
+    // but uncacheable: Dynamic Page Cache is running, but not caching.
...
+    // Verify that by default, Drupal has no auto-placeholdering cache tags.

I think we can remove all of this too, then, based on #36.

Vidushi Mehta’s picture

Status: Needs work » Needs review
StatusFileSize
new84.95 KB

@mondrake I was also thinking the same but got confused so only removed the single quote comments mentioned in #36. Added a new patch and removed the line space error as well.

mondrake’s picture

Status: Needs review » Reviewed & tested by the community

Thanks. Attached rawdiff between #34 and #40.

mondrake’s picture

StatusFileSize
new2.82 KB
Vidushi Mehta’s picture

Thanks for adding. I forgot to add the interdiff.

paulocs’s picture

StatusFileSize
new4.87 KB

Interdiff between patch #37 and #40.
Also RTBC+1

Cheers, Paulo.

catch’s picture

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

#40 looks great, but needs a re-roll for 9.1.x

Vidushi Mehta’s picture

Assigned: Unassigned » Vidushi Mehta
mondrake’s picture

Since we're at it, please remove also the CS issue from #40, see https://www.drupal.org/pift-ci-job/1803208

mondrake’s picture

Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs reroll
StatusFileSize
new84.75 KB
new3.36 KB

Rerolled

Vidushi Mehta’s picture

@mondrake I've already created the patch and worked on so many file. Put efforts and time on that but now its not worth.

Vidushi Mehta’s picture

Assigned: Vidushi Mehta » Unassigned
mondrake’s picture

@Vidushi Mehta oops sorry, I missed that. Apologies.

  • catch committed 3e0f015 on 9.1.x
    Issue #3131186 by mondrake, Vidushi Mehta, longwave, paulocs, xjm:...
catch’s picture

Status: Reviewed & tested by the community » Fixed

     // GET a URL that was cached by Page Cache before, it should not be now.
-    $this->drupalGet('/respond-cacheable-response');
-    $this->assertNull($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.');
+    $this->drupalGet('/respond-cacheable-reponse');
+    $this->assertSession()->responseHeaderDoesNotExist('X-Drupal-Cache');
   }
 

The patch introduced a typo here, but still passed tests because the 403 response is also not cached. Out of scope to improve the test here, but we should open a follow-up to add an additional assertion to make sure the page we get back is valid.

Committed 3e0f015 and pushed to 9.1.x. Thanks!

Status: Fixed » Closed (fixed)

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