itsgoingd / clockwork

Clockwork - php dev tools in your browser - server-side component
https://underground.works/clockwork
MIT License
5.69k stars 321 forks source link

Incompatibility with PHPUnit 10 (Pest 2) - overriding final method #627

Closed CadenP closed 9 months ago

CadenP commented 1 year ago

Attempting to run a test with PHPUnit 10 (or Pest 2) which has the UsesClockwork trait causes the test to fail with the following message:

PHP Fatal error:  Cannot override final method PHPUnit\Framework\Assert::assertThat() in /opt/project/vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/Tests/UsesClockwork.php on line 71

According to PHPUnit's changelog:

The public methods of PHPUnit\Framework\Assert and PHPUnit\Framework\TestCase are now final

Pest 2 requires PHPUnit 10, so it shares the incompatibility.

kdevan commented 1 year ago

+1 here, running into this too. And I love using clockwork with tests!

Edit: Looks like this would need to use the new events system: https://localheinz.com/articles/2023/02/14/extending-phpunit-with-its-new-event-system/#content-events-in-the-new-event-system

Here's an example of a phpunit 10 extension using the new events system (from the article above): https://github.com/localheinz/phpunit-speedtrap/tree/feature/phpunit-10

I got this running again in a very hacky way, without collecting asserts by using the modified UsesClockwork trait below. It at least does collect all of the other information like performance, logging, events, etc. Once you remove the assertThat function that can't be overloaded anymore, there's a few more errors because of PHPUnit\Runner\BaseTestRunner being removed and status methods changing. In the file below I've replaced those with the new classes/functions so at least it runs.

It does seem like with the phpunit 10 update that using events is required here. I really did a very cursory dive into this just now so take all this information with a grain of salt.

<?php

namespace Tests;

use Clockwork\Helpers\Serializer;
use Clockwork\Helpers\StackFilter;
use Clockwork\Helpers\StackTrace;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\TestStatus\TestStatus;

// Trait to include in PHPUnit tests to collect executed tests
trait UsesClockwork
{
    // Clockwork phpunit metadata collected while executing tests
    protected static $clockwork = [
        'asserts' => [],
    ];

    // Set up Clockwork in this test case, should be called from the PHPUnit setUp method
    protected function setUpClockwork()
    {
        if (! $this->app->make('clockwork.support')->isCollectingTests()) {
        return;
        }

        $this->beforeApplicationDestroyed(function () {
            if ($this->app->make('clockwork.support')->isTestFiltered($this->toString())) {
            return;
            }

            $this->app->make('clockwork')
                ->resolveAsTest(
                    $this->toString(),
                    $this->resolveClockworkStatus(),
                    $this->status()->message(),
                    $this->resolveClockworkAsserts()
                )
                ->storeRequest();
        });
    }

    // Resolve Clockwork test status
    protected function resolveClockworkStatus()
    {
        $status = $this->status()->asInt();

        $statuses = [
            TestStatus::unknown()->asInt() => 'unknown',
            TestStatus::success()->asInt() => 'success',
            TestStatus::skipped()->asInt() => 'skipped',
            TestStatus::incomplete()->asInt() => 'incomplete',
            TestStatus::failure()->asInt() => 'failure',
            TestStatus::error()->asInt() => 'error',
            TestStatus::risky()->asInt() => 'risky',
            TestStatus::warning()->asInt() => 'warning',
        ];

        return isset($statuses[$status]) ? $statuses[$status] : null;
    }

    // Resolve executed asserts
    protected function resolveClockworkAsserts()
    {
        $asserts = static::$clockwork['asserts'];

        if ($this->status() == TestStatus::failure() && count($asserts)) {
            $asserts[count($asserts) - 1]['success'] = false;
        }

        static::$clockwork['asserts'] = [];

        return $asserts;
    }

    // Overload the main PHPUnit assert method to collect executed asserts
    // public static function assertThat($value, Constraint $constraint, string $message = ''): void
    // {
    //     $trace = StackTrace::get(['arguments' => true, 'limit' => 10]);

    //     $assertFrame = $trace->filter(function ($frame) {
    //     return strpos($frame->function, 'assert') === 0;
    //     })->last();
    //     $trace = $trace->skip(StackFilter::make()->isNotVendor(['itsgoingd', 'phpunit']))->limit(3);

    //     static::$clockwork['asserts'][] = [
    //         'name' => $assertFrame->function,
    //         'arguments' => $assertFrame->args,
    //         'trace' => (new Serializer)->trace($trace),
    //         'passed' => true,
    //     ];

    //     parent::assertThat($value, $constraint, $message);
    // }
}
itsgoingd commented 1 year ago

Hey, I wrote some very suspect php code and implemented a new Clockwork extension for PHPUnit 10. I would appreciate if you could try it out, since I don't have any good PHPUnit 10 test-suite for testing.

To try the new extension, update Clockwork to dev-master, remove the UsesClockwork trait and add following to your phpunit.xml:

<extensions>
    <bootstrap class="Clockwork\Support\Laravel\Tests\ClockworkExtension" />
</extensions>
kdevan commented 1 year ago

Just tried it out and ran some tests and it is working great so far! This is awesome!!! Thank you so much, I love this project.

If I run into anything I'll let you know.

CadenP commented 1 year ago

I tested it as well. Works great! Thanks for making this work.

walterdis commented 1 year ago

Hi. Do i have to configure something else after enabling 'collect' => env('CLOCKWORK_TESTS_COLLECT', true), and using the extension?

I'm runing php artisan test and php artisan test --filter="SomeControllerTest" and its not working.

Thanks ;)

itsgoingd commented 1 year ago

@walterdis Yeah, adding the PHPUnit extension and setting CLOCKWORK_TESTS_COLLECT=true should work. If it doesn't work, can you try updating to the latest master?

walterdis commented 1 year ago

@walterdis Yeah, adding the PHPUnit extension and setting CLOCKWORK_TESTS_COLLECT=true should work. If it doesn't work, can you try updating to the latest master?

Worked!! Thanks a lot ;)

itsgoingd commented 9 months ago

Now available in Clockwork 5.2.