Problem/Motivation

In #3131186-14: Replace assertions involving calls to drupalGetHeader() with session-based assertions, where possible we discovered we are missing a straightforward method to determine if a response's named header exists, or does not exist.

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

mondrake created an issue. See original summary.

mondrake’s picture

Title: Introduce PHPUnit compliant WebAssert::responseHeaderExist method, and it negative » Introduce PHPUnit compliant WebAssert::responseHeaderExist method, and its negative
Issue summary: View changes
Status: Active » Needs review
FileSize
4.03 KB

Start here.

mondrake’s picture

Title: Introduce PHPUnit compliant WebAssert::responseHeaderExist method, and its negative » Introduce PHPUnit compliant WebAssert::responseHeaderExists method, and its negative

Status: Needs review » Needs work

The last submitted patch, 2: 3133355-2.patch, failed testing. View results

mondrake’s picture

Assigned: Unassigned » mondrake

on this

mondrake’s picture

Assigned: mondrake » Unassigned
Issue summary: View changes
Status: Needs work » Needs review
FileSize
4.9 KB
7.03 KB

With tests. Conversions of existing tests could be done here, but I'd rather do them in #3131186: Replace assertions involving calls to drupalGetHeader() with session-based assertions, where possible.

mondrake’s picture

Issue summary: View changes
mondrake’s picture

Issue summary: View changes
jungle’s picture

Looks great! two nitpicks:

  1. +++ b/core/tests/Drupal/FunctionalTests/WebAssertTest.php
    @@ -0,0 +1,52 @@
    +   * Modules to enable.
    +   *
    +   * @var array
    

    Use {@inheritdoc} ?

  2. +++ b/core/tests/Drupal/FunctionalTests/WebAssertTest.php
    @@ -0,0 +1,52 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected $defaultTheme = 'classy';
    

    Replace with stark? see [#3083055], classy is not recommended to use anymore.

jungle’s picture

Status: Needs review » Needs work
mondrake’s picture

Thanks @jungle. Fixed #9.

jungle’s picture

Status: Needs review » Reviewed & tested by the community

To me, the patch for 9.x is an RTBC and this should be eligible backporting to 8.x.

longwave’s picture

+++ b/core/tests/Drupal/Tests/WebAssert.php
@@ -55,6 +58,38 @@ protected function cleanUrl($url) {
+    $constraint = new ArrayHasKey($name);

Is it worth the effort of creating brand new Constraint classes? If this was a new ResponseHeaderExists class then we can customise the "Failed asserting that an array has the key" message. Unfortunately ArrayHasKey is final so we can't just extend it, but Constraint is quite straighforward to implement.

longwave’s picture

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

Trying #13, unsure if this is overkill though - comments welcome.

This is ineligible for backport to 8.x due to the void return type not being compatible with PHP 7.0.

longwave’s picture

FileSize
5.98 KB
2.86 KB

Fixed a couple of minor errors in #14. Interdiff from #11.

jungle’s picture

Status: Needs review » Needs work

Even better, besides 3 coding standards messages

core/tests/Drupal/Tests/Constraints/ResponseHeaderExists.php
- line 52 Expected 1 blank line after function; 0 found
- line 53 The closing brace for the class must have an empty line before it
core/tests/Drupal/Tests/WebAssert.php
- line 15 Unused use statement

This is ineligible for backport to 8.x due to the void return type not being compatible with PHP 7.0.

You are right.

Thank you @longwave!

ravi.shankar’s picture

Status: Needs work » Needs review
FileSize
6.21 KB
1.17 KB

Here I have added a patch which might address comment #16.

jungle’s picture

Status: Needs review » Reviewed & tested by the community

Thanks, @ravi.shankar! No more CS violations.

#17 is the one RTBCed, And this is ineligible to 8.x as @longwave said "due to the void return type not being compatible with PHP 7.0."

mondrake’s picture

Assigned: Unassigned » mondrake
Status: Reviewed & tested by the community » Needs work

Reviewing.

mondrake’s picture

Assigned: mondrake » Unassigned
Status: Needs work » Needs review

Conceptually +1 for #13 and following, but I think we should do that in a separate issue, because:

  1. IMHO a new constraint class for each possible usage of checking the existence of an array key, just to have a specific message instead of the standard one, is overkill. We can easily end up with dozens of classes all functionally identical to each other but for the strings used to report the failure. Lots of code to maintain.
  2. Maybe we can just try to 'generalize' the concept, and pass to the constraint method also a string describing what the array represents, and one describing what a key represents, so that the failure string can be composed like

    Failed asserting that the {array_represents} has a {key_represents} named '{key_name}'.

    In code, something like

        $constraint = new DescribedArrayHasKey($name, 'response', 'header');
    

    or maybe using setters

        $constraint = new DescribedArrayHasKey($name);
        $constraint
          ->arrayIsDescribedAsA('response')
          ->keyIsDescribedAsA('header');
    

    so that the constraint can be re-used for other purposes.

  3. It would make sense, if we want to go for the above, to try and tackle other custom assertions in the same issue to make the generic constraint as reusable as possible.
  4. We definitely need unit tests for the new constraint class (even if we do not go for the above).
  5. IMHO the right namespace for this classes will be in TestTools, i.e. namespace Drupal\TestTools\Constraint.
  6. Too bad PHPUnit has made its own constraint classes final... this would have been a nice extension of PHPUnit\Framework\Constrain\ArrayHasKey.
jungle’s picture

@mondrake, thanks for your review!

Should proper design pattern(s) be taken into account here for reusing existed code? Such as: builder, decorator, composite etc.

mondrake’s picture

#21 no idea - even stronger reason for a separate issue.

longwave’s picture

Just spotted that Symfony ships with some very similar constraints - e.g. https://github.com/symfony/http-foundation/blob/4.4/Test/Constraint/Resp... - but we can't use them directly because they expect a Symfony Response object which Mink doesn't use.

mondrake’s picture

xjm’s picture

Title: Introduce PHPUnit compliant WebAssert::responseHeaderExists method, and its negative » Introduce PHPUnit-compliant WebAssert::responseHeaderExists() method, and its negative
xjm’s picture

Status: Reviewed & tested by the community » Needs work
  1. So in #3131186-16: Replace assertions involving calls to drupalGetHeader() with session-based assertions, where possible, part of the reason we're introducing these methods is that a response header may exist but have a null value.

    Based on that, I think that we should have test coverage that responseHeaderExists() passes if it exists and is set to NULL, and that responseHeaderNotExists() fails in the same circumstance.

  2. +++ b/core/tests/Drupal/FunctionalTests/WebAssertTest.php
    @@ -0,0 +1,50 @@
    + * Tests WebAssert functionality.
    ...
    +class WebAssertTest extends BrowserTestBase {
    

    It tests quite a bit less than that. :) Let's give it a more specific name and document it?

  3. +++ b/core/tests/Drupal/FunctionalTests/WebAssertTest.php
    @@ -0,0 +1,50 @@
    +  /**
    +   * @covers ::responseHeaderExists
    +   */
    +  public function testResponseHeaderExists() {
    ...
    +  /**
    +   * @covers ::responseHeaderDoesNotExist
    +   */
    +  public function testResponseHeaderDoesNotExist() {
    

    Technically, these should both still have one-line summaries.

  4. +++ b/core/tests/Drupal/Tests/WebAssert.php
    @@ -55,6 +58,38 @@ protected function cleanUrl($url) {
    +   * @param string $name
    +   *   The name of the header entry to check existence of.
    +   */
    +  public function responseHeaderExists(string $name, string $message = ''): void {
    ...
    +   * @param string $name
    +   *   The name of the header entry to check existence of.
    +   */
    +  public function responseHeaderDoesNotExist(string $name, string $message = ''): void {
    

    Documentation of $message is missing.

  5. Finally, can we include with the updated patch an updated test-only patch to expose the fails of the final version? Thanks!

longwave’s picture

#26.2 None of WebAssert has tests. I think this class should exist with the aim to add further WebAssert tests to it in the future.

#26.3 There is an exception in our PHPunit coding standards: "PHPUnit tests MAY skip the PHPDoc summary line if their PHPDoc specifies a @coversDefaultClass" Actually does this only apply to the *class* comment and not the method comment?

longwave’s picture

Status: Needs work » Needs review
FileSize
4.96 KB
6.85 KB
4.36 KB

Addressed #26.1, #26.3, #26.4, Not sure what a test-only patch adds in this case, but uploading one anyway.

The last submitted patch, 28: 3133355-28-test-only.patch, failed testing. View results

xjm’s picture

Issue tags: +Needs followup

@longwave, I hear all the time that such-and-such class is intended to be extended with more tests for X, and then it isn't. :) If the test is to be expanded, we'll want a followup issue that specifies adding other coverage to the test in order for that to be likely to happen. Thanks!

xjm’s picture

Couple questions from reviewing this from an architectural standpoint rather than just "is this committable" tunnel vision:

  1. +++ b/core/tests/Drupal/Tests/WebAssert.php
    @@ -55,6 +58,42 @@ protected function cleanUrl($url) {
    +   * @param string $message
    +   *   The failure message.
    

    Interesting, I did not notice before that this is a "Failure message" rather than the "Pass" message. Definitely upside-down from our historical tests, at least maybe the SimpleTest ones. Is that the convention for PHPUnit assertions and I just never realized?

  2. +++ b/core/tests/Drupal/Tests/WebAssert.php
    @@ -55,6 +58,42 @@ protected function cleanUrl($url) {
    +    if ($message === '') {
    +      $message = "Failed asserting that the response does not have a '$name' header.";
    +    }
    

    Something I opened an issue for many, many years ago was to concatenate the user-supplied message (if any) with the default message, so that (a) people did not obscure real debugging with less specific or misleading messages but also (b) They could add more information without having to also ensure that the generic message details were included. That was back in pre-PHPUnit days, but it's still relevant to this patch.

    I'm not sure how much we're trying to closely follow specific upstream conventions here, but it's worth considering at least whether that's something we'd want to do.

mondrake’s picture

Priority: Normal » Major
Issue tags: -Needs followup +blocker

#30 I would need the test class in #3143870: Calls to AssertLegacyTrait::assertUrl() have wrong number of arguments to add a test for WebAssert::addressEquals to ensure that querystring, if present in the address to be tested, are taken into account.

mondrake’s picture

Priority: Major » Normal

Did not mean to change to major, sorry.

mondrake’s picture

Status: Needs review » Reviewed & tested by the community

Not sure I understand what is still left to do here.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 28: 3133355-28.patch, failed testing. View results

mondrake’s picture

Status: Needs work » Reviewed & tested by the community

Random test failure in #35.

alexpott’s picture

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

We still need the followup to add tests for the rest of our methods in WebAssert - it would be great to add similar test coverage for them.

Also the questions in #31 have not been addressed

mondrake’s picture

Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs followup

Filed #3159783: [meta] Add tests for the WebAssert class.

#31.1 - there's no guidance in PHPUnit as to the $message argument. Actually, there's thinking to eventually remove them for good. See https://github.com/sebastianbergmann/phpunit/issues/4198. PHPUnit's default messages always start with 'Failed asserting that...' so they're somewhat negative. For Drupal we're proposing a convention in #3131946: [META] [policy] Remove PHPUnit assertion messages when possible, and standardize remaining messages.

#31.2 - AFAICS in PHPUnit there's no way to concatenate default and custome message, they're two separate things that are both printed when a test fails. If no custom message is given, only the default is printed.

catch’s picture

Status: Reviewed & tested by the community » Fixed

All the feedback has been addressed, so I think this is ready to go in.

Committed 6e7d0d9 and pushed to 9.1.x. Thanks!

  • catch committed 6e7d0d9 on 9.1.x
    Issue #3133355 by longwave, mondrake, ravi.shankar, jungle, xjm:...

Status: Fixed » Closed (fixed)

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