Co-PHPUnit
A PHPUnit extension that enables tests to run within Swoole coroutines, specifically designed for testing Hyperf applications and other Swoole-based frameworks.
Installation
composer require friendsofhyperf/co-phpunit --devWhy Co-PHPUnit?
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:
- Coroutine context being unavailable
- Timers and event loops not working correctly
- Coordinator pattern failures
- Database connection pool problems
Co-PHPUnit addresses these issues by automatically wrapping test execution within Swoole coroutine contexts when needed.
Usage
Basic Usage
Simply use the RunTestsInCoroutine trait in your test class:
<?php
namespace Your\Namespace\Tests;
use FriendsOfHyperf\CoPHPUnit\Concerns\RunTestsInCoroutine;
use PHPUnit\Framework\TestCase;
class YourTest extends TestCase
{
use RunTestsInCoroutine;
public function testSomething()
{
// Your test code
// This will automatically run within a Swoole coroutine
}
}Disabling Coroutine for Specific Tests
If you need to disable coroutine execution for a particular test class, set the $enableCoroutine property to false:
<?php
namespace Your\Namespace\Tests;
use FriendsOfHyperf\CoPHPUnit\Concerns\RunTestsInCoroutine;
use PHPUnit\Framework\TestCase;
class YourTest extends TestCase
{
use RunTestsInCoroutine;
protected bool $enableCoroutine = false;
public function testSomething()
{
// This test will run in normal synchronous mode
}
}How It Works
The RunTestsInCoroutine trait overrides PHPUnit's runBare() method to:
- Check Prerequisites: Verify that the Swoole extension is loaded and not already in a coroutine context
- Create Coroutine Context: Wrap test execution within
Swoole\Coroutine\run() - Exception Handling: Properly catch and re-throw exceptions from within coroutines
- Cleanup: Clear all timers and restore the coordinator when tests complete
- Fallback: Fall back to normal test execution if conditions aren't met
PHPUnit Patch
This 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 automatically applied when the package autoloads.
Requirements
- PHP >= 8.0
- PHPUnit >= 10.0
- Swoole extension (when running tests in coroutine mode)
- Hyperf >= 3.1 (for coordinator functionality)
Configuration
Composer Autoload
The package automatically registers its autoload files in composer.json:
{
"autoload-dev": {
"psr-4": {
"Your\\Tests\\": "tests/"
},
"files": [
"vendor/friendsofhyperf/co-phpunit/phpunit-patch.php"
]
}
}PHPUnit Configuration
No special PHPUnit configuration is required. The package works seamlessly with your existing phpunit.xml configuration.
Best Practices
- Use for Integration Tests: This is particularly useful for integration tests that interact with Hyperf's coroutine-aware components
- Enable Selectively: Not all tests need to run in coroutines. Use
$enableCoroutine = falsefor unit tests that don't require coroutine context - Test Isolation: The package automatically cleans up timers and coordinator state between tests
- Performance: Tests running in coroutines may have slightly different performance characteristics
Example: Testing Hyperf Services
<?php
namespace App\Tests;
use FriendsOfHyperf\CoPHPUnit\Concerns\RunTestsInCoroutine;
use Hyperf\Context\ApplicationContext;
use PHPUnit\Framework\TestCase;
class ServiceTest extends TestCase
{
use RunTestsInCoroutine;
public function testServiceWithCoroutineContext()
{
// Get service from container
$service = ApplicationContext::getContainer()->get(YourService::class);
// Test methods that use coroutine context
$result = $service->asyncOperation();
$this->assertNotNull($result);
}
public function testDatabaseConnection()
{
// Test database operations that require connection pools
$result = Db::table('users')->first();
$this->assertIsArray($result);
}
}Troubleshooting
Tests Hanging or Timing Out
If tests hang, ensure:
- All async operations are properly awaited
- No infinite loops exist in coroutine callbacks
- Timers are cleared during test teardown
"Call to a member function on null"
This typically indicates coroutine context is unavailable. Ensure:
- Swoole extension is installed and enabled
- The
RunTestsInCoroutinetrait is included $enableCoroutineis set totrue
PHPUnit Version Compatibility
This package supports PHPUnit 10.x, 11.x, and 12.x. Make sure your PHPUnit version is compatible.