Skip to content

AddDoesNotPerformAssertionToNonAssertingTestRector: misses assertions outside the test method body (parent::, expectUserDeprecationMessage(), lifecycle methods) #9965

Open

Description

@bbrala
Subject Details
Rector version 2.8.4 (rector-phpunit main has the same code)
PHPUnit 12.5.34

AddDoesNotPerformAssertionToNonAssertingTestRector only looks at assertions reachable from the test method body. PHPUnit counts assertions from more places, so tests that do assert get #[DoesNotPerformAssertions] and then fail as risky.

Minimal PHP Code Causing Issue

<?php

namespace Repro;

use PHPUnit\Framework\TestCase;

abstract class BaseReproTestCase extends TestCase
{
    public function testInherited(): void
    {
        $this->assertTrue(true);
    }
}

final class ReproTest extends BaseReproTestCase
{
    public function testInherited(): void
    {
        parent::testInherited();
    }

    public function testDeprecation(): void
    {
        $this->expectUserDeprecationMessage('Some deprecation');
        @trigger_error('Some deprecation', E_USER_DEPRECATED);
    }

    public function testDirect(): void
    {
        $this->assertTrue(true);
    }
}

final class LifecycleReproTest extends TestCase
{
    private bool $ready = false;

    protected function setUp(): void
    {
        $this->ready = true;
        $this->assertTrue($this->ready);
    }

    public function testSomething(): void
    {
        $value = 1 + 1;
    }
}

Responsible rules

  • AddDoesNotPerformAssertionToNonAssertingTestRector

Actual Behavior

#[DoesNotPerformAssertions] is added to ReproTest::testInherited(), ReproTest::testDeprecation() and LifecycleReproTest::testSomething(). With failOnRisky, all three then fail:

This test is not expected to perform assertions but performed 1 assertion

Expected Behavior

None of them is changed. Only tests that really perform no assertions should get the attribute.

Cause

  1. expectUserDeprecationMessage() and expectUserDeprecationMessageMatches() are missing from AssertCallAnalyzer::ASSERT_METHOD_NAME_PREFIXES. PHPUnit counts every expected deprecation as an assertion (TestCase::verifyDeprecationExpectations() increments numberOfAssertionsPerformed per expectation).
  2. parent::testInherited() is not followed into the parent method. AssertCallAnalyzer::resolveClassMethodFromCall() resolves a StaticCall through the type of $call->class, and for parent the type resolver returns a union of all ancestors instead of a single class type.
  3. Assertions in lifecycle methods (setUp(), tearDown(), assertPreConditions(), assertPostConditions(), #[Before]/#[After]/#[PreCondition]/#[PostCondition] methods) are not considered, although PHPUnit counts them toward every test of the class.

Fix: rectorphp/rector-phpunit#822

Context

We found this running the composer-based PHPUnit set on Drupal core (https://www.drupal.org/i/3638148), where 68 tests became risky. 17 use expectUserDeprecationMessage(), 22 only call parent::test*() (config entity validation tests), and most of the rest assert in base-class lifecycle methods. Running the rule over core's test directories gives 83 methods on rector-phpunit main and 3 with the PR above.

Activity

  1. bbrala commented on Nov 14, 2026

    @bbrala
    Author

    (fix also incoming ;))

  2. changed the title [-]AddDoesNotPerformAssertionToNonAssertingTestRector: misses parent::test*() calls and expectUserDeprecationMessage()[/-] [+]AddDoesNotPerformAssertionToNonAssertingTestRector: misses assertions outside the test method body (parent::, expectUserDeprecationMessage(), lifecycle methods)[/+] on Nov 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions