Skip to content

Commit 8b2f19e

Browse files
committed
feat: Clean-up listener/subscriber after the test
Fixes #74
1 parent e01b288 commit 8b2f19e

6 files changed

+144
-5
lines changed

classes/MockDisablerPHPUnit10.php

+14-1
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,22 @@ class MockDisablerPHPUnit10 implements FinishedSubscriber
2222
* @var Deactivatable The function mocks.
2323
*/
2424
private $deactivatable;
25+
26+
/**
27+
* @var callable|null The callback to execute after the test.
28+
*/
29+
private $callback;
2530

2631
/**
2732
* Sets the function mocks.
2833
*
2934
* @param Deactivatable $deactivatable The function mocks.
35+
* @param callback|null $callback The callback to execute after the test.
3036
*/
31-
public function __construct(Deactivatable $deactivatable)
37+
public function __construct(Deactivatable $deactivatable, ?callable $callback = null)
3238
{
3339
$this->deactivatable = $deactivatable;
40+
$this->callback = $callback;
3441
}
3542

3643
/**
@@ -39,10 +46,16 @@ public function __construct(Deactivatable $deactivatable)
3946
public function notify(Finished $event) : void
4047
{
4148
$this->deactivatable->disable();
49+
if ($this->callback !== null) {
50+
call_user_func($this->callback, $this);
51+
}
4252
}
4353

4454
public function endTest(): void
4555
{
4656
$this->deactivatable->disable();
57+
if ($this->callback !== null) {
58+
call_user_func($this->callback, $this);
59+
}
4760
}
4861
}

classes/MockDisablerPHPUnit6.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,22 @@ class MockDisablerPHPUnit6 extends BaseTestListener
2222
* @var Deactivatable The function mocks.
2323
*/
2424
private $deactivatable;
25+
26+
/**
27+
* @var callable|null The callback to execute after the test.
28+
*/
29+
private $callback;
2530

2631
/**
2732
* Sets the function mocks.
2833
*
2934
* @param Deactivatable $deactivatable The function mocks.
35+
* @param callable|null $callback The callback to execute after the test.
3036
*/
31-
public function __construct(Deactivatable $deactivatable)
37+
public function __construct(Deactivatable $deactivatable, callable $callback = null)
3238
{
3339
$this->deactivatable = $deactivatable;
40+
$this->callback = $callback;
3441
}
3542

3643
/**
@@ -46,5 +53,8 @@ public function endTest(Test $test, $time)
4653
parent::endTest($test, $time);
4754

4855
$this->deactivatable->disable();
56+
if ($this->callback !== null) {
57+
call_user_func($this->callback, $this);
58+
}
4959
}
5060
}

classes/MockDisablerPHPUnit7.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,22 @@ class MockDisablerPHPUnit7 extends BaseTestListener
2222
* @var Deactivatable The function mocks.
2323
*/
2424
private $deactivatable;
25+
26+
/**
27+
* @var callable|null The callback to execute after the test.
28+
*/
29+
private $callback;
2530

2631
/**
2732
* Sets the function mocks.
2833
*
2934
* @param Deactivatable $deactivatable The function mocks.
35+
* @param callable|null $callback The callback to execute after the test.
3036
*/
31-
public function __construct(Deactivatable $deactivatable)
37+
public function __construct(Deactivatable $deactivatable, $callback = null)
3238
{
3339
$this->deactivatable = $deactivatable;
40+
$this->callback = $callback;
3441
}
3542

3643
/**
@@ -46,5 +53,8 @@ public function endTest(Test $test, float $time) : void
4653
parent::endTest($test, $time);
4754

4855
$this->deactivatable->disable();
56+
if ($this->callback !== null) {
57+
call_user_func($this->callback, $this);
58+
}
4959
}
5060
}

classes/PHPMock.php

+29-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPUnit\Event\Facade;
1010
use PHPUnit\Framework\MockObject\MockObject;
1111
use ReflectionClass;
12+
use ReflectionMethod;
1213
use ReflectionProperty;
1314
use SebastianBergmann\Template\Template;
1415

@@ -117,8 +118,32 @@ public function registerForTearDown(Deactivatable $deactivatable)
117118
$property->setAccessible(true);
118119
$property->setValue($facade, false);
119120

121+
$method = new ReflectionMethod($facade, 'deferredDispatcher');
122+
$method->setAccessible(true);
123+
$dispatcher = $method->invoke($facade);
124+
125+
$propDispatcher = new ReflectionProperty($dispatcher, 'dispatcher');
126+
$propDispatcher->setAccessible(true);
127+
$directDispatcher = $propDispatcher->getValue($dispatcher);
128+
129+
$propSubscribers = new ReflectionProperty($directDispatcher, 'subscribers');
130+
$propSubscribers->setAccessible(true);
131+
120132
$facade->registerSubscriber(
121-
new MockDisabler($deactivatable)
133+
new MockDisabler(
134+
$deactivatable,
135+
static function (MockDisabler $original) use ($directDispatcher, $propSubscribers) {
136+
$subscribers = $propSubscribers->getValue($directDispatcher);
137+
138+
foreach ($subscribers['PHPUnit\Event\Test\Finished'] as $key => $subscriber) {
139+
if ($original === $subscriber) {
140+
unset($subscribers['PHPUnit\Event\Test\Finished'][$key]);
141+
}
142+
}
143+
144+
$propSubscribers->setValue($directDispatcher, $subscribers);
145+
}
146+
)
122147
);
123148

124149
$property->setValue($facade, true);
@@ -127,7 +152,9 @@ public function registerForTearDown(Deactivatable $deactivatable)
127152
}
128153

129154
$result = $this->getTestResultObject();
130-
$result->addListener(new MockDisabler($deactivatable));
155+
$result->addListener(new MockDisabler($deactivatable, static function (MockDisabler $listener) use ($result) {
156+
$result->removeListener($listener);
157+
}));
131158
}
132159

133160
/**

tests/MockDisablerTest.php

+19
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace phpmock\phpunit;
44

5+
use phpmock\Deactivatable;
56
use phpmock\Mock;
67
use PHPUnit\Framework\TestCase;
78

@@ -32,4 +33,22 @@ public function testEndTest()
3233

3334
$this->assertEquals(1, min(1, 9));
3435
}
36+
37+
public function testCallback()
38+
{
39+
$executed = false;
40+
$executedWith = null;
41+
$mock = $this->createMock(Deactivatable::class);
42+
$disabler = new MockDisabler($mock, static function ($disabler) use (&$executed, &$executedWith) {
43+
self::assertInstanceOf(MockDisabler::class, $disabler);
44+
45+
$executed = true;
46+
$executedWith = $disabler;
47+
});
48+
49+
$disabler->endTest($this, 1);
50+
51+
self::assertTrue($executed);
52+
self::assertSame($executedWith, $disabler);
53+
}
3554
}

tests/PHPMockTest.php

+60
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace phpmock\phpunit;
44

55
use phpmock\AbstractMockTestCase;
6+
use phpmock\Deactivatable;
67
use PHPUnit\Framework\ExpectationFailedException;
78

89
/**
@@ -63,4 +64,63 @@ public function testFunctionMockFailsExpectation()
6364
time(); // satisfy the expectation
6465
}
6566
}
67+
68+
/**
69+
* Register a Deactivatable for a tear down.
70+
*
71+
* @test
72+
*/
73+
public function testRegisterForTearDownRegistered()
74+
{
75+
$obj = new \stdClass();
76+
$obj->count = 0;
77+
78+
$class = new class ($obj) implements Deactivatable
79+
{
80+
private $obj;
81+
82+
public function __construct($obj)
83+
{
84+
$this->obj = $obj;
85+
}
86+
87+
public function disable()
88+
{
89+
++$this->obj->count;
90+
}
91+
};
92+
$this->registerForTearDown($class);
93+
94+
self::assertSame(0, $obj->count);
95+
96+
return $obj;
97+
}
98+
99+
/**
100+
* Check the Deactivatable was executed on a tear down of dependent test.
101+
*
102+
* @test
103+
*
104+
* @depends testRegisterForTearDownRegistered
105+
*/
106+
#[\PHPUnit\Framework\Attributes\Depends('testRegisterForTearDownRegistered')]
107+
public function testRegisterForTearDownExecuted($obj)
108+
{
109+
self::assertSame(1, $obj->count);
110+
111+
return $obj;
112+
}
113+
114+
/**
115+
* Check the Deactivatable was unregistered after executing, so it is not executed again.
116+
*
117+
* @test
118+
*
119+
* @depends testRegisterForTearDownExecuted
120+
*/
121+
#[\PHPUnit\Framework\Attributes\Depends('testRegisterForTearDownExecuted')]
122+
public function testRegisterForTearDownRemoved($obj)
123+
{
124+
self::assertSame(1, $obj->count);
125+
}
66126
}

0 commit comments

Comments
 (0)