Skip to content

Commit 57dcff2

Browse files
authored
feat: extract co-phpunit package for coroutine testing support (#968)
* feat: extract co-phpunit package for coroutine testing support Extract PHPUnit coroutine testing functionality into a standalone package to improve code organization and reusability. Changes: - Create new co-phpunit package with RunTestsInCoroutine trait - Move phpunit-patch.php from tests to co-phpunit package - Update composer.json to include co-phpunit in autoload and replace sections - Update tests to import RunTestsInCoroutine from new package location - Clean up test autoload configuration * Remove duplicate ConfigProvider entry in composer.json Eliminated a repeated 'FriendsOfHyperf\CommandBenchmark\ConfigProvider' entry from the providers list to prevent redundancy. * docs(co-phpunit): add comprehensive README documentation * Add co-phpunit package and update docs Added the co-phpunit package with initial configuration, workflows, funding, and license files. Updated documentation and profile README to include co-phpunit in the components list. * Remove empty autoload-dev files from composer.json Updated composer-json-fixer to unset autoload-dev files when empty, and removed the empty files array from composer.json. This prevents unnecessary empty entries in the autoload-dev section. * feat: add hyperf/coordinator requirement to composer.json --------- Co-authored-by: Deeka Wong <[email protected]>
1 parent dbdf06c commit 57dcff2

File tree

14 files changed

+323
-16
lines changed

14 files changed

+323
-16
lines changed

bin/composer-json-fixer

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,9 @@ $require = [];
2727
$autoload = [];
2828
$autoloadFiles = [];
2929
$autoloadDev = [
30-
'FriendsOfHyperf\\Tests\\' => 'tests/',
31-
];
32-
$autoloadDevFiles = [
33-
'tests/phpunit-patch.php',
30+
'FriendsOfHyperf\Tests\\' => 'tests/',
3431
];
32+
$autoloadDevFiles = [];
3533
$configProviders = [];
3634
$replaces = [];
3735

@@ -86,7 +84,11 @@ $json = json_decode(file_get_contents(__DIR__ . '/../composer.json'));
8684
$json->autoload->files = $autoloadFiles;
8785
$json->autoload->{'psr-4'} = $autoload;
8886
$json->{'autoload-dev'}->{'psr-4'} = $autoloadDev;
89-
$json->{'autoload-dev'}->files = $autoloadDevFiles;
87+
if (! empty($autoloadDevFiles)) {
88+
$json->{'autoload-dev'}->files = $autoloadDevFiles;
89+
} else {
90+
unset($json->{'autoload-dev'}->files);
91+
}
9092
$json->extra->hyperf->config = $configProviders;
9193
$json->replace = $replaces;
9294

composer.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
"replace": {
119119
"friendsofhyperf/amqp-job": "*",
120120
"friendsofhyperf/cache": "*",
121+
"friendsofhyperf/co-phpunit": "*",
121122
"friendsofhyperf/command-benchmark": "*",
122123
"friendsofhyperf/command-signals": "*",
123124
"friendsofhyperf/command-validation": "*",
@@ -174,6 +175,7 @@
174175
"psr-4": {
175176
"FriendsOfHyperf\\AmqpJob\\": "src/amqp-job/src/",
176177
"FriendsOfHyperf\\Cache\\": "src/cache/src/",
178+
"FriendsOfHyperf\\CoPHPUnit\\": "src/co-phpunit/src/",
177179
"FriendsOfHyperf\\CommandBenchmark\\": "src/command-benchmark/src/",
178180
"FriendsOfHyperf\\CommandSignals\\": "src/command-signals/src/",
179181
"FriendsOfHyperf\\CommandValidation\\": "src/command-validation/src/",
@@ -226,6 +228,7 @@
226228
},
227229
"files": [
228230
"src/amqp-job/src/Functions.php",
231+
"src/co-phpunit/phpunit-patch.php",
229232
"src/encryption/src/Functions.php",
230233
"src/exception-event/src/Functions.php",
231234
"src/helpers/src/Command/Functions.php",
@@ -243,10 +246,7 @@
243246
"autoload-dev": {
244247
"psr-4": {
245248
"FriendsOfHyperf\\Tests\\": "tests/"
246-
},
247-
"files": [
248-
"tests/phpunit-patch.php"
249-
]
249+
}
250250
},
251251
"config": {
252252
"allow-plugins": {

docs/zh-cn/guide/start/components.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
|--|--|--|--|
77
|[amqp-job](https://github.com/friendsofhyperf/amqp-job)|[![Latest Stable Version](https://poser.pugx.org/friendsofhyperf/amqp-job/v)](https://packagist.org/packages/friendsofhyperf/amqp-job)|[![Total Downloads](https://poser.pugx.org/friendsofhyperf/amqp-job/downloads)](https://packagist.org/packages/friendsofhyperf/amqp-job)|[![Monthly Downloads](https://poser.pugx.org/friendsofhyperf/amqp-job/d/monthly)](https://packagist.org/packages/friendsofhyperf/amqp-job)|
88
|[cache](https://github.com/friendsofhyperf/cache)|[![Latest Stable Version](https://poser.pugx.org/friendsofhyperf/cache/v)](https://packagist.org/packages/friendsofhyperf/cache)|[![Total Downloads](https://poser.pugx.org/friendsofhyperf/cache/downloads)](https://packagist.org/packages/friendsofhyperf/cache)|[![Monthly Downloads](https://poser.pugx.org/friendsofhyperf/cache/d/monthly)](https://packagist.org/packages/friendsofhyperf/cache)|
9+
|[co-phpunit](https://github.com/friendsofhyperf/co-phpunit)|[![Latest Stable Version](https://poser.pugx.org/friendsofhyperf/co-phpunit/v)](https://packagist.org/packages/friendsofhyperf/co-phpunit)|[![Total Downloads](https://poser.pugx.org/friendsofhyperf/co-phpunit/downloads)](https://packagist.org/packages/friendsofhyperf/co-phpunit)|[![Monthly Downloads](https://poser.pugx.org/friendsofhyperf/co-phpunit/d/monthly)](https://packagist.org/packages/friendsofhyperf/co-phpunit)|
910
|[command-benchmark](https://github.com/friendsofhyperf/command-benchmark)|[![Latest Stable Version](https://poser.pugx.org/friendsofhyperf/command-benchmark/v)](https://packagist.org/packages/friendsofhyperf/command-benchmark)|[![Total Downloads](https://poser.pugx.org/friendsofhyperf/command-benchmark/downloads)](https://packagist.org/packages/friendsofhyperf/command-benchmark)|[![Monthly Downloads](https://poser.pugx.org/friendsofhyperf/command-benchmark/d/monthly)](https://packagist.org/packages/friendsofhyperf/command-benchmark)|
1011
|[command-signals](https://github.com/friendsofhyperf/command-signals)|[![Latest Stable Version](https://poser.pugx.org/friendsofhyperf/command-signals/v)](https://packagist.org/packages/friendsofhyperf/command-signals)|[![Total Downloads](https://poser.pugx.org/friendsofhyperf/command-signals/downloads)](https://packagist.org/packages/friendsofhyperf/command-signals)|[![Monthly Downloads](https://poser.pugx.org/friendsofhyperf/command-signals/d/monthly)](https://packagist.org/packages/friendsofhyperf/command-signals)|
1112
|[command-validation](https://github.com/friendsofhyperf/command-validation)|[![Latest Stable Version](https://poser.pugx.org/friendsofhyperf/command-validation/v)](https://packagist.org/packages/friendsofhyperf/command-validation)|[![Total Downloads](https://poser.pugx.org/friendsofhyperf/command-validation/downloads)](https://packagist.org/packages/friendsofhyperf/command-validation)|[![Monthly Downloads](https://poser.pugx.org/friendsofhyperf/command-validation/d/monthly)](https://packagist.org/packages/friendsofhyperf/command-validation)|

src/.github/profile/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
|--|--|--|--|
77
|[amqp-job](https://github.com/friendsofhyperf/amqp-job)|[![Latest Stable Version](https://poser.pugx.org/friendsofhyperf/amqp-job/v)](https://packagist.org/packages/friendsofhyperf/amqp-job)|[![Total Downloads](https://poser.pugx.org/friendsofhyperf/amqp-job/downloads)](https://packagist.org/packages/friendsofhyperf/amqp-job)|[![Monthly Downloads](https://poser.pugx.org/friendsofhyperf/amqp-job/d/monthly)](https://packagist.org/packages/friendsofhyperf/amqp-job)|
88
|[cache](https://github.com/friendsofhyperf/cache)|[![Latest Stable Version](https://poser.pugx.org/friendsofhyperf/cache/v)](https://packagist.org/packages/friendsofhyperf/cache)|[![Total Downloads](https://poser.pugx.org/friendsofhyperf/cache/downloads)](https://packagist.org/packages/friendsofhyperf/cache)|[![Monthly Downloads](https://poser.pugx.org/friendsofhyperf/cache/d/monthly)](https://packagist.org/packages/friendsofhyperf/cache)|
9+
|[co-phpunit](https://github.com/friendsofhyperf/co-phpunit)|[![Latest Stable Version](https://poser.pugx.org/friendsofhyperf/co-phpunit/v)](https://packagist.org/packages/friendsofhyperf/co-phpunit)|[![Total Downloads](https://poser.pugx.org/friendsofhyperf/co-phpunit/downloads)](https://packagist.org/packages/friendsofhyperf/co-phpunit)|[![Monthly Downloads](https://poser.pugx.org/friendsofhyperf/co-phpunit/d/monthly)](https://packagist.org/packages/friendsofhyperf/co-phpunit)|
910
|[command-benchmark](https://github.com/friendsofhyperf/command-benchmark)|[![Latest Stable Version](https://poser.pugx.org/friendsofhyperf/command-benchmark/v)](https://packagist.org/packages/friendsofhyperf/command-benchmark)|[![Total Downloads](https://poser.pugx.org/friendsofhyperf/command-benchmark/downloads)](https://packagist.org/packages/friendsofhyperf/command-benchmark)|[![Monthly Downloads](https://poser.pugx.org/friendsofhyperf/command-benchmark/d/monthly)](https://packagist.org/packages/friendsofhyperf/command-benchmark)|
1011
|[command-signals](https://github.com/friendsofhyperf/command-signals)|[![Latest Stable Version](https://poser.pugx.org/friendsofhyperf/command-signals/v)](https://packagist.org/packages/friendsofhyperf/command-signals)|[![Total Downloads](https://poser.pugx.org/friendsofhyperf/command-signals/downloads)](https://packagist.org/packages/friendsofhyperf/command-signals)|[![Monthly Downloads](https://poser.pugx.org/friendsofhyperf/command-signals/d/monthly)](https://packagist.org/packages/friendsofhyperf/command-signals)|
1112
|[command-validation](https://github.com/friendsofhyperf/command-validation)|[![Latest Stable Version](https://poser.pugx.org/friendsofhyperf/command-validation/v)](https://packagist.org/packages/friendsofhyperf/command-validation)|[![Total Downloads](https://poser.pugx.org/friendsofhyperf/command-validation/downloads)](https://packagist.org/packages/friendsofhyperf/command-validation)|[![Monthly Downloads](https://poser.pugx.org/friendsofhyperf/command-validation/d/monthly)](https://packagist.org/packages/friendsofhyperf/command-validation)|

src/co-phpunit/.gitattributes

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/.github export-ignore
2+
/.vscode export-ignore
3+
/tests export-ignore
4+
.gitattributes export-ignore

src/co-phpunit/.github/FUNDING.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github: huangdijia
2+
custom: https://hdj.me/sponsors/
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: Close Pull Request
2+
3+
on:
4+
pull_request_target:
5+
types: [opened]
6+
7+
jobs:
8+
run:
9+
uses: friendsofhyperf/.github/.github/workflows/close-pull-request.yml@main
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*.*.*"
7+
8+
jobs:
9+
build:
10+
uses: friendsofhyperf/.github/.github/workflows/release.yaml@main

src/co-phpunit/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) D.J.Hwang
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

src/co-phpunit/README.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# Co-PHPUnit
2+
3+
[![Latest Stable Version](https://img.shields.io/packagist/v/friendsofhyperf/co-phpunit)](https://packagist.org/packages/friendsofhyperf/co-phpunit)
4+
[![Total Downloads](https://img.shields.io/packagist/dt/friendsofhyperf/co-phpunit)](https://packagist.org/packages/friendsofhyperf/co-phpunit)
5+
[![License](https://img.shields.io/packagist/l/friendsofhyperf/co-phpunit)](https://github.com/friendsofhyperf/components)
6+
7+
A PHPUnit extension that enables tests to run inside Swoole coroutines, specifically designed for testing Hyperf applications and other Swoole-based frameworks.
8+
9+
## Installation
10+
11+
```bash
12+
composer require friendsofhyperf/co-phpunit --dev
13+
```
14+
15+
## Why Co-PHPUnit?
16+
17+
When testing Hyperf applications, many components rely on Swoole's coroutine context to function properly. Running tests in a traditional synchronous environment can lead to issues such as:
18+
19+
- Coroutine context not being available
20+
- Timers and event loops not working correctly
21+
- Coordinator pattern failures
22+
- Database connection pool issues
23+
24+
Co-PHPUnit solves these problems by automatically wrapping test execution in a Swoole coroutine context when needed.
25+
26+
## Usage
27+
28+
### Basic Usage
29+
30+
Simply use the `RunTestsInCoroutine` trait in your test class:
31+
32+
```php
33+
<?php
34+
35+
namespace Your\Namespace\Tests;
36+
37+
use FriendsOfHyperf\CoPHPUnit\Concerns\RunTestsInCoroutine;
38+
use PHPUnit\Framework\TestCase;
39+
40+
class YourTest extends TestCase
41+
{
42+
use RunTestsInCoroutine;
43+
44+
public function testSomething()
45+
{
46+
// Your test code here
47+
// This will automatically run inside a Swoole coroutine
48+
}
49+
}
50+
```
51+
52+
### Disabling Coroutine for Specific Tests
53+
54+
If you need to disable coroutine execution for a specific test class, set the `$enableCoroutine` property to `false`:
55+
56+
```php
57+
<?php
58+
59+
namespace Your\Namespace\Tests;
60+
61+
use FriendsOfHyperf\CoPHPUnit\Concerns\RunTestsInCoroutine;
62+
use PHPUnit\Framework\TestCase;
63+
64+
class YourTest extends TestCase
65+
{
66+
use RunTestsInCoroutine;
67+
68+
protected bool $enableCoroutine = false;
69+
70+
public function testSomething()
71+
{
72+
// This test will run in normal synchronous mode
73+
}
74+
}
75+
```
76+
77+
## How It Works
78+
79+
The `RunTestsInCoroutine` trait overrides PHPUnit's `runBare()` method to:
80+
81+
1. **Check Prerequisites**: Verifies that the Swoole extension is loaded and not already in a coroutine context
82+
2. **Create Coroutine Context**: Wraps test execution in `Swoole\Coroutine\run()`
83+
3. **Exception Handling**: Properly captures and re-throws exceptions from within the coroutine
84+
4. **Cleanup**: Clears all timers and resumes coordinator on test completion
85+
5. **Fallback**: If conditions aren't met, falls back to normal test execution
86+
87+
### PHPUnit Patch
88+
89+
The package includes a `phpunit-patch.php` file that automatically removes the `final` keyword from PHPUnit's `TestCase::runBare()` method, allowing the trait to override it. This patch is applied automatically when the package is autoloaded.
90+
91+
## Requirements
92+
93+
- PHP >= 8.0
94+
- PHPUnit >= 10.0
95+
- Swoole extension (when running tests in coroutine mode)
96+
- Hyperf >= 3.1 (for coordinator functionality)
97+
98+
## Configuration
99+
100+
### Composer Autoload
101+
102+
The package automatically registers its autoload files in composer.json:
103+
104+
```json
105+
{
106+
"autoload-dev": {
107+
"psr-4": {
108+
"Your\\Tests\\": "tests/"
109+
},
110+
"files": [
111+
"vendor/friendsofhyperf/co-phpunit/phpunit-patch.php"
112+
]
113+
}
114+
}
115+
```
116+
117+
### PHPUnit Configuration
118+
119+
No special PHPUnit configuration is required. The package works seamlessly with your existing `phpunit.xml` configuration.
120+
121+
## Best Practices
122+
123+
1. **Use for Integration Tests**: This is particularly useful for integration tests that interact with Hyperf's coroutine-aware components
124+
2. **Selective Enablement**: Not all tests need to run in coroutines. Use `$enableCoroutine = false` for unit tests that don't require coroutine context
125+
3. **Test Isolation**: The package automatically cleans up timers and coordinator state between tests
126+
4. **Performance**: Tests running in coroutines may have slightly different performance characteristics
127+
128+
## Example: Testing Hyperf Services
129+
130+
```php
131+
<?php
132+
133+
namespace App\Tests;
134+
135+
use FriendsOfHyperf\CoPHPUnit\Concerns\RunTestsInCoroutine;
136+
use Hyperf\Context\ApplicationContext;
137+
use PHPUnit\Framework\TestCase;
138+
139+
class ServiceTest extends TestCase
140+
{
141+
use RunTestsInCoroutine;
142+
143+
public function testServiceWithCoroutineContext()
144+
{
145+
// Get service from container
146+
$service = ApplicationContext::getContainer()->get(YourService::class);
147+
148+
// Test methods that use coroutine context
149+
$result = $service->asyncOperation();
150+
151+
$this->assertNotNull($result);
152+
}
153+
154+
public function testDatabaseConnection()
155+
{
156+
// Test database operations that require connection pool
157+
$result = Db::table('users')->first();
158+
159+
$this->assertIsArray($result);
160+
}
161+
}
162+
```
163+
164+
## Troubleshooting
165+
166+
### Tests Hang or Timeout
167+
168+
If tests hang, ensure that:
169+
- All async operations are properly awaited
170+
- No infinite loops exist in coroutine callbacks
171+
- Timers are cleared in test teardown
172+
173+
### "Call to a member function on null"
174+
175+
This usually indicates that coroutine context is not available. Ensure:
176+
- Swoole extension is installed and enabled
177+
- The `RunTestsInCoroutine` trait is included
178+
- `$enableCoroutine` is set to `true`
179+
180+
### PHPUnit Version Compatibility
181+
182+
The package supports PHPUnit 10.x, 11.x, and 12.x. Ensure your PHPUnit version is compatible.
183+
184+
## Contributing
185+
186+
Contributions are welcome! Please feel free to submit a Pull Request.
187+
188+
## Support
189+
190+
- Issues: [GitHub Issues](https://github.com/friendsofhyperf/components/issues)
191+
- Documentation: [Hyperf Fans](https://hyperf.fans)
192+
- Pull Requests: [GitHub Pull Requests](https://github.com/friendsofhyperf/components/pulls)
193+
194+
## License
195+
196+
This package is open-sourced software licensed under the [MIT license](LICENSE).
197+
198+
## Credits
199+
200+
- [huangdijia](https://github.com/huangdijia)
201+
- [All Contributors](https://github.com/friendsofhyperf/components/graphs/contributors)

0 commit comments

Comments
 (0)