testo/lifecycle
Lifecycle hooks plugin for the Testo PHP testing framework. Adds setup/teardown around individual tests and entire test classes to manage fixtures, external resources, and cleanup between runs. Install via composer require --dev testo/lifecycle.
Install the Package Add the package to your project via Composer:
composer require --dev testo/lifecycle
Enable the Plugin
Register the lifecycle plugin in your Testo configuration (typically in testo.php or via CLI):
use Testo\Lifecycle\LifecyclePlugin;
return [
'plugins' => [
LifecyclePlugin::class,
],
];
First Use Case: Basic Hooks Annotate a test class with lifecycle hooks:
use Testo\Annotations\BeforeClass;
use Testo\Annotations\AfterClass;
use Testo\Annotations\Before;
use Testo\Annotations\After;
#[BeforeClass]
public function setUpClass(): void
{
// Runs once before all tests in the class
$this->sharedResource = new SharedResource();
}
#[AfterClass]
public function tearDownClass(): void
{
// Runs once after all tests in the class
$this->sharedResource->cleanup();
}
#[Before]
public function setUp(): void
{
// Runs before each test
$this->testData = $this->generateTestData();
}
#[After]
public function tearDown(): void
{
// Runs after each test
unset($this->testData);
}
public function test_example(): void
{
// Your test logic
}
Run Tests Execute your tests using Testo’s CLI:
./vendor/bin/testo
Class-Level Lifecycle Management
Use @BeforeClass and @AfterClass for setup/teardown that should run once per test class (e.g., database connections, API clients, or heavy fixtures).
#[BeforeClass]
public function createTestDatabase(): void
{
$this->db = new TestDatabase();
$this->db->seed();
}
#[AfterClass]
public function dropTestDatabase(): void
{
$this->db->drop();
}
Per-Test Lifecycle Management
Use @Before and @After for setup/teardown that should run before/after each test (e.g., temporary files, mocks, or test-specific data).
#[Before]
public function setUpTestData(): void
{
$this->testUser = User::factory()->create();
}
#[After]
public function deleteTestData(): void
{
$this->testUser->delete();
}
Conditional Hooks Dynamically enable/disable hooks based on test conditions or environment variables:
#[Before]
public function conditionalSetup(): void
{
if (getenv('TEST_WITH_MOCKS')) {
$this->mockService = $this->createMock(Service::class);
}
}
Dependency Injection Inject dependencies into lifecycle methods via Testo’s DI container:
#[BeforeClass]
public function setUpWithDependencies(Logger $logger): void
{
$this->logger = $logger;
$this->logger->info('Setting up test class');
}
Shared Fixtures Use class-level hooks to load shared fixtures for all tests in a class:
#[BeforeClass]
public function loadSharedFixtures(): void
{
FixtureLoader::load('shared_fixtures.yaml');
}
Fixture-Driven Testing
@BeforeClass to load fixtures once per class.@AfterClass to clean up fixtures.Resource Management
@Before to open connections/files/services.@After to close/clean up resources.Test Isolation
@Before.@After to avoid side effects.Hybrid Testing
@BeforeClass and use it across tests.Leverage Testo’s Annotations
Familiarize yourself with Testo’s annotation system (@test, @group, etc.) to combine lifecycle hooks with other test metadata.
Combine with Testo Plugins
Integrate testo/lifecycle with other Testo plugins (e.g., testo/database for database testing) for seamless workflows:
use Testo\Database\DatabasePlugin;
return [
'plugins' => [
DatabasePlugin::class,
LifecyclePlugin::class,
],
];
Custom Hook Logic Extend the lifecycle behavior by creating custom methods and calling them from hooks:
#[Before]
public function prepareTestEnvironment(): void
{
$this->setupMocks();
$this->configureTestData();
}
private function setupMocks(): void
{
// Custom mock setup logic
}
Test Organization
Group related tests into classes and use @BeforeClass/@AfterClass to manage shared resources efficiently.
CI/CD Optimization Use lifecycle hooks to optimize test runs in CI by:
@BeforeClass).Hook Execution Order
@BeforeClass, @AfterClass) run once per class, while per-test hooks (@Before, @After) run before/after each test.@BeforeClass completes before @Before and @AfterClass runs after @After.@AfterClass runs after all tests, including failures.State Leakage
Exception Handling
@BeforeClass can prevent tests from running.Annotation Conflicts
@Before must be on a method, not a property).Plugin Compatibility
Performance Overhead
@Before/@After for lightweight operations can slow down tests.Hook Not Triggering
testo.php.#[]).Testo\Annotations\Before directly in code to test hook execution:
#[Before]
public function debugHook(): void
{
error_log('Hook executed!');
}
Hook Failing Silently
#[Before]
public function setupWithErrorHandling(): void
{
try {
$this->setupLogic();
} catch (\Throwable $e) {
error_log('Setup failed: ' . $e->getMessage());
throw $e;
}
}
Test Isolation Issues
@After.@After to verify cleanup:
#[After]
public function verifyCleanup(): void
{
$this->assertNull($this->testData, 'Test data not cleaned up!');
}
Plugin Registration Errors
LifecyclePlugin::class is correctly spelled and autoloaded.composer clear-cache
composer install
Annotation Syntax
#[Before]) instead of @Before if your PHP version supports attributes (PHP 8+).@ syntax.Method Visibility
Static vs. Instance Methods
How can I help you explore Laravel packages today?