diff --git a/classes/MockDisablerPHPUnit10.php b/classes/MockDisablerPHPUnit10.php index be536b0..853e387 100644 --- a/classes/MockDisablerPHPUnit10.php +++ b/classes/MockDisablerPHPUnit10.php @@ -2,6 +2,7 @@ namespace phpmock\phpunit; +use Closure; use phpmock\Deactivatable; use PHPUnit\Event\Test\Finished; use PHPUnit\Event\Test\FinishedSubscriber; @@ -22,15 +23,22 @@ class MockDisablerPHPUnit10 implements FinishedSubscriber * @var Deactivatable The function mocks. */ private $deactivatable; + + /** + * @var Closure|null The callback to execute after the test. + */ + private $callback; /** * Sets the function mocks. * * @param Deactivatable $deactivatable The function mocks. + * @param Closure|null $callback The callback to execute after the test. */ - public function __construct(Deactivatable $deactivatable) + public function __construct(Deactivatable $deactivatable, ?Closure $callback = null) { $this->deactivatable = $deactivatable; + $this->callback = $callback; } /** @@ -39,10 +47,16 @@ public function __construct(Deactivatable $deactivatable) public function notify(Finished $event) : void { $this->deactivatable->disable(); + if ($this->callback !== null) { + ($this->callback)($this); + } } public function endTest(): void { $this->deactivatable->disable(); + if ($this->callback !== null) { + ($this->callback)($this); + } } } diff --git a/classes/MockDisablerPHPUnit6.php b/classes/MockDisablerPHPUnit6.php index 3db2fcc..c2926ab 100644 --- a/classes/MockDisablerPHPUnit6.php +++ b/classes/MockDisablerPHPUnit6.php @@ -2,6 +2,7 @@ namespace phpmock\phpunit; +use Closure; use phpmock\Deactivatable; use PHPUnit\Framework\BaseTestListener; use PHPUnit\Framework\Test; @@ -22,15 +23,22 @@ class MockDisablerPHPUnit6 extends BaseTestListener * @var Deactivatable The function mocks. */ private $deactivatable; + + /** + * @var Closure|null The callback to execute after the test. + */ + private $callback; /** * Sets the function mocks. * * @param Deactivatable $deactivatable The function mocks. + * @param Closure|null $callback The callback to execute after the test. */ - public function __construct(Deactivatable $deactivatable) + public function __construct(Deactivatable $deactivatable, Closure $callback = null) { $this->deactivatable = $deactivatable; + $this->callback = $callback; } /** @@ -46,5 +54,8 @@ public function endTest(Test $test, $time) parent::endTest($test, $time); $this->deactivatable->disable(); + if ($this->callback !== null) { + ($this->callback)($this); + } } } diff --git a/classes/MockDisablerPHPUnit7.php b/classes/MockDisablerPHPUnit7.php index 44c5141..41def45 100644 --- a/classes/MockDisablerPHPUnit7.php +++ b/classes/MockDisablerPHPUnit7.php @@ -2,6 +2,7 @@ namespace phpmock\phpunit; +use Closure; use phpmock\Deactivatable; use PHPUnit\Framework\BaseTestListener; use PHPUnit\Framework\Test; @@ -22,15 +23,22 @@ class MockDisablerPHPUnit7 extends BaseTestListener * @var Deactivatable The function mocks. */ private $deactivatable; + + /** + * @var Closure|null The callback to execute after the test. + */ + private $callback; /** * Sets the function mocks. * * @param Deactivatable $deactivatable The function mocks. + * @param Closure|null $callback The callback to execute after the test. */ - public function __construct(Deactivatable $deactivatable) + public function __construct(Deactivatable $deactivatable, Closure $callback = null) { $this->deactivatable = $deactivatable; + $this->callback = $callback; } /** @@ -46,5 +54,8 @@ public function endTest(Test $test, float $time) : void parent::endTest($test, $time); $this->deactivatable->disable(); + if ($this->callback !== null) { + ($this->callback)($this); + } } } diff --git a/classes/PHPMock.php b/classes/PHPMock.php index 4575504..c75a9d3 100644 --- a/classes/PHPMock.php +++ b/classes/PHPMock.php @@ -7,8 +7,10 @@ use phpmock\MockBuilder; use phpmock\Deactivatable; use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\Finished; use PHPUnit\Framework\MockObject\MockObject; use ReflectionClass; +use ReflectionMethod; use ReflectionProperty; use SebastianBergmann\Template\Template; @@ -117,8 +119,32 @@ public function registerForTearDown(Deactivatable $deactivatable) $property->setAccessible(true); $property->setValue($facade, false); + $method = new ReflectionMethod($facade, 'deferredDispatcher'); + $method->setAccessible(true); + $dispatcher = $method->invoke($facade); + + $propDispatcher = new ReflectionProperty($dispatcher, 'dispatcher'); + $propDispatcher->setAccessible(true); + $directDispatcher = $propDispatcher->getValue($dispatcher); + + $propSubscribers = new ReflectionProperty($directDispatcher, 'subscribers'); + $propSubscribers->setAccessible(true); + $facade->registerSubscriber( - new MockDisabler($deactivatable) + new MockDisabler( + $deactivatable, + static function (MockDisabler $original) use ($directDispatcher, $propSubscribers) { + $subscribers = $propSubscribers->getValue($directDispatcher); + + foreach ($subscribers[Finished::class] as $key => $subscriber) { + if ($original === $subscriber) { + unset($subscribers[Finished::class][$key]); + } + } + + $propSubscribers->setValue($directDispatcher, $subscribers); + } + ) ); $property->setValue($facade, true); @@ -127,7 +153,9 @@ public function registerForTearDown(Deactivatable $deactivatable) } $result = $this->getTestResultObject(); - $result->addListener(new MockDisabler($deactivatable)); + $result->addListener(new MockDisabler($deactivatable, static function (MockDisabler $listener) use ($result) { + $result->removeListener($listener); + })); } /** diff --git a/tests/MockDisablerTest.php b/tests/MockDisablerTest.php index ba7a805..4dbadca 100644 --- a/tests/MockDisablerTest.php +++ b/tests/MockDisablerTest.php @@ -2,6 +2,7 @@ namespace phpmock\phpunit; +use phpmock\Deactivatable; use phpmock\Mock; use PHPUnit\Framework\TestCase; @@ -32,4 +33,22 @@ public function testEndTest() $this->assertEquals(1, min(1, 9)); } + + public function testCallback() + { + $executed = false; + $executedWith = null; + $mock = $this->createMock(Deactivatable::class); + $disabler = new MockDisabler($mock, static function ($disabler) use (&$executed, &$executedWith) { + self::assertInstanceOf(MockDisabler::class, $disabler); + + $executed = true; + $executedWith = $disabler; + }); + + $disabler->endTest($this, 1); + + self::assertTrue($executed); + self::assertSame($executedWith, $disabler); + } } diff --git a/tests/PHPMockTest.php b/tests/PHPMockTest.php index da26159..582b1c7 100644 --- a/tests/PHPMockTest.php +++ b/tests/PHPMockTest.php @@ -3,6 +3,7 @@ namespace phpmock\phpunit; use phpmock\AbstractMockTestCase; +use phpmock\Deactivatable; use PHPUnit\Framework\ExpectationFailedException; /** @@ -63,4 +64,63 @@ public function testFunctionMockFailsExpectation() time(); // satisfy the expectation } } + + /** + * Register a Deactivatable for a tear down. + * + * @test + */ + public function testRegisterForTearDownRegistered() + { + $obj = new \stdClass(); + $obj->count = 0; + + $class = new class ($obj) implements Deactivatable + { + private $obj; + + public function __construct($obj) + { + $this->obj = $obj; + } + + public function disable() + { + ++$this->obj->count; + } + }; + $this->registerForTearDown($class); + + self::assertSame(0, $obj->count); + + return $obj; + } + + /** + * Check the Deactivatable was executed on a tear down of dependent test. + * + * @test + * + * @depends testRegisterForTearDownRegistered + */ + #[\PHPUnit\Framework\Attributes\Depends('testRegisterForTearDownRegistered')] + public function testRegisterForTearDownExecuted($obj) + { + self::assertSame(1, $obj->count); + + return $obj; + } + + /** + * Check the Deactivatable was unregistered after executing, so it is not executed again. + * + * @test + * + * @depends testRegisterForTearDownExecuted + */ + #[\PHPUnit\Framework\Attributes\Depends('testRegisterForTearDownExecuted')] + public function testRegisterForTearDownRemoved($obj) + { + self::assertSame(1, $obj->count); + } }