Skip to content

Conversation

@binaryfire
Copy link
Contributor

Summary

This PR ports Laravel Orchestral Testbench's testing infrastructure to Hypervel, bringing familiar testing patterns and PHP 8 attribute-based test configuration to Hypervel.

Key Features Ported from Laravel Testbench

  • defineEnvironment() hook - Pre-boot application configuration, matching Laravel's defineEnvironment() pattern
  • Package provider/alias registration - getPackageProviders() and getPackageAliases() methods for package testing
  • Route definition helpers - defineRoutes() and defineWebRoutes() with automatic web middleware group wrapping
  • Database migration hooks - defineDatabaseMigrations(), destroyDatabaseMigrations(), defineDatabaseSeeders()
  • PHP 8 Testing Attributes - Full attribute-based test configuration system

Testing Attributes (Following Laravel Testbench Patterns)

Attribute Description
#[WithConfig('key', 'value')] Set config values directly
#[DefineEnvironment('methodName')] Call a method with $app for environment setup
#[DefineRoute('methodName')] Call a method with $router for route definition
#[DefineDatabase('methodName')] Call a method for database setup (supports deferred execution)
#[WithMigration('path')] Load migration paths
#[RequiresEnv('VAR')] Skip test if environment variable is missing
#[ResetRefreshDatabaseState] Reset database state between test classes
#[Define('group', 'method')] Meta-attribute shorthand (resolves to DefineEnvironment/DefineDatabase/DefineRoute)

Lifecycle Hooks (Matching Laravel Testbench)

  • BeforeAll / AfterAll - Class-level lifecycle (static, runs once per test class)
  • BeforeEach / AfterEach - Test-level lifecycle (runs for each test method)
  • Invokable - Direct invocation with app instance
  • Actionable - Method delegation with closure callback

Hypervel-Specific Adaptations

While following Laravel Testbench patterns closely, this implementation includes Hypervel-specific adaptations:

  • Coroutine context awareness - BeforeEach/AfterEach attributes execute inside runInCoroutine(), matching where setUpTraits() runs in Foundation TestCase
  • Hypervel Router API - Route group middleware uses Hypervel's $router->group() signature
  • Automatic route registration - setUpApplicationRoutes() is called automatically in afterApplicationCreated, so routes defined via defineRoutes() are available without manual setup
  • Smart web routes handling - Uses reflection to skip empty web routes group registration, preventing interference with Hypervel's RouteFileCollector when defineWebRoutes() isn't overridden
  • No route caching - Skipped as Hypervel/Hyperf doesn't have Laravel-style route caching
  • No annotation/pest support - Simplified TestingFeature orchestrator without legacy patterns

Example Usage

#[WithConfig('app.name', 'Test App')]
#[ResetRefreshDatabaseState]
class MyPackageTest extends \Hypervel\Testbench\TestCase
{
    protected function getPackageProviders($app): array
    {
        return [MyServiceProvider::class];
    }

    protected function defineRoutes($router): void
    {
        $router->get('/api/test', fn() => 'ok');
    }

    protected function defineWebRoutes($router): void
    {
        $router->get('/test', fn() => 'ok'); // Has 'web' middleware
    }

    #[DefineEnvironment('setupCustomEnv')]
    #[Define('env', 'setupAnotherEnv')]  // Meta-attribute shorthand
    public function testSomething(): void
    {
        $this->assertEquals('Test App', config('app.name'));
    }

    protected function setupCustomEnv($app): void
    {
        $app->get('config')->set('custom.key', 'value');
    }
}

Files Changed

New Files (28)

Foundation Contracts (src/foundation/src/Testing/Contracts/Attributes/):

  • TestingFeature.php, Resolvable.php, Actionable.php, Invokable.php
  • BeforeEach.php, AfterEach.php, BeforeAll.php, AfterAll.php

Foundation Attributes (src/foundation/src/Testing/Attributes/):

  • DefineEnvironment.php, WithConfig.php, DefineRoute.php, DefineDatabase.php
  • WithMigration.php, RequiresEnv.php, ResetRefreshDatabaseState.php, Define.php

Foundation Infrastructure (src/foundation/src/Testing/):

  • AttributeParser.php - Parses class/method attributes with Resolvable support
  • Concerns/HandlesAttributes.php - Attribute parsing trait
  • Concerns/InteractsWithTestCase.php - Caching and lifecycle execution
  • Features/TestingFeature.php - Orchestrator for default + attribute flows
  • Features/FeaturesCollection.php - Collection for deferred callbacks

Testbench Traits (src/testbench/src/Concerns/):

  • CreatesApplication.php - Package providers/aliases
  • HandlesRoutes.php - Route definition helpers
  • HandlesDatabases.php - Database migration helpers

Tests (7 test files with comprehensive coverage)

Modified Files (3)

  • src/foundation/src/Testing/Concerns/InteractsWithContainer.php - Added defineEnvironment() hook
  • src/testbench/src/TestCase.php - Integrated new traits with coroutine-aware lifecycle
  • tests/Sanctum/AuthenticateRequestsTest.php - Refactored to use new testbench pattern (getPackageProviders, defineEnvironment, defineRoutes)

Phase 1: defineEnvironment hook
- Add defineEnvironment($app) call in refreshApplication() before app boot
- Add empty defineEnvironment() method for subclass override
- Add DefineEnvironmentTest

Phase 2: Attribute contracts
- TestingFeature: marker interface
- Resolvable: resolve() for meta-attributes (does NOT extend TestingFeature)
- Actionable: handle($app, Closure $action)
- Invokable: __invoke($app)
- BeforeEach/AfterEach: per-test lifecycle hooks
- BeforeAll/AfterAll: per-class lifecycle hooks

Phase 3: AttributeParser and FeaturesCollection
- AttributeParser: parses class/method attributes with inheritance and Resolvable support
- FeaturesCollection: collection for deferred attribute callbacks

Phase 4.2: HandlesAttributes trait
- parseTestMethodAttributes() for executing attribute callbacks
- Static caching for class/method attributes
- usesTestingConcern() to check trait usage
- usesTestingFeature() for programmatic attribute registration
- resolvePhpUnitAttributes() merges all attribute sources
- Lifecycle methods: setUpTheTestEnvironmentUsingTestCase,
  tearDownTheTestEnvironmentUsingTestCase,
  setUpBeforeClassUsingTestCase, tearDownAfterClassUsingTestCase
Simplified orchestrator for default + attribute flows.
Uses inline flag-based memoization instead of Orchestra's once() helper.
No annotation/pest support - not needed for Hypervel.
- DefineEnvironment: calls test method with $app
- WithConfig: sets config value directly
- DefineRoute: calls test method with $router
- DefineDatabase: deferred execution, resets RefreshDatabaseState
- ResetRefreshDatabaseState: resets database state before/after all tests
- WithMigration: loads explicit migration paths
- RequiresEnv: skips test if env var missing
- Define: meta-attribute resolving to env/db/route attributes
…rastructure

- Change Actionable::handle() implementations to return mixed instead of void
- Change Invokable::__invoke() implementations to return mixed instead of void
- Fix TestingFeature orchestrator closure return type
- Add @phpstan-ignore for rescue callback type resolution
- Update setUpTheTestEnvironmentUsingTestCase() to execute all attribute types (Invokable, Actionable, BeforeEach)
- Call setUpApplicationRoutes() automatically in afterApplicationCreated
- Add reflection check to skip empty web routes group registration
- Refactor Sanctum tests to use new testbench pattern (getPackageProviders,
  defineEnvironment, defineRoutes)
- Add tests for route accessibility and routing without defineWebRoutes
…ritance

- Mark parseTestMethodAttributes() as @internal to prevent misuse
- Add test verifying Define meta-attribute is resolved by AttributeParser
- Add test verifying Define meta-attribute is executed through lifecycle
- Add tests verifying attributes are inherited from parent TestCase classes
Add proper type hints following existing codebase conventions:
- Use `ApplicationContract` alias for contract type hints
- Use `Router` type hints for route definition methods
- Update all contracts, attributes, traits, and test files

This improves type safety while maintaining consistency with the
existing codebase pattern where 20+ files use the ApplicationContract
alias convention.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant