diff --git a/src/Concerns/ApplicationTestingHooks.php b/src/Concerns/ApplicationTestingHooks.php new file mode 100644 index 000000000..57ce9c471 --- /dev/null +++ b/src/Concerns/ApplicationTestingHooks.php @@ -0,0 +1,247 @@ + + */ + protected $afterApplicationCreatedCallbacks = []; + + /** + * The callbacks that should be run after the application is refreshed. + * + * @var array + */ + protected $afterApplicationRefreshedCallbacks = []; + + /** + * The callbacks that should be run before the application is destroyed. + * + * @var array + */ + protected $beforeApplicationDestroyedCallbacks = []; + + /** + * The exception thrown while running an application destruction callback. + * + * @var \Throwable|null + */ + protected $callbackException; + + /** + * Setup the testing hooks. + * + * @param (\Closure():(void))|null $callback + * @return void + */ + final protected function setUpTheApplicationTestingHooks(?Closure $callback = null): void + { + if (! $this->app) { + $this->refreshApplication(); + + $this->setUpParallelTestingCallbacks(); + } + + /** @var \Illuminate\Foundation\Application $app */ + $app = $this->app; + + $this->callAfterApplicationRefreshedCallbacks(); + + if (! \is_null($callback)) { + \call_user_func($callback); + } + + $this->callAfterApplicationCreatedCallbacks(); + + Model::setEventDispatcher($app['events']); + } + + /** + * Teardown the testing hooks. + * + * @param (\Closure():(void))|null $callback + * @return void + * + * @throws \Throwable + */ + final protected function tearDownTheApplicationTestingHooks(?Closure $callback = null): void + { + if ($this->app) { + $this->callBeforeApplicationDestroyedCallbacks(); + + $this->tearDownParallelTestingCallbacks(); + + $this->app?->flush(); + + $this->app = null; + } + + if (! \is_null($callback)) { + \call_user_func($callback); + } + + if (class_exists(Mockery::class)) { + if ($container = Mockery::getContainer()) { + $this->addToAssertionCount($container->mockery_getExpectationCount()); + } + + Mockery::close(); + } + + Carbon::setTestNow(); + + if (class_exists(CarbonImmutable::class)) { + CarbonImmutable::setTestNow(); + } + + $this->afterApplicationCreatedCallbacks = []; + $this->beforeApplicationDestroyedCallbacks = []; + + Artisan::forgetBootstrappers(); + Component::flushCache(); + Component::forgetComponentsResolver(); + Component::forgetFactory(); + Queue::createPayloadUsing(null); + HandleExceptions::forgetApp(); + + if ($this->callbackException) { + throw $this->callbackException; + } + } + + /** + * Setup parallel testing callback. + */ + protected function setUpParallelTestingCallbacks(): void + { + if (class_exists(ParallelTesting::class) && $this instanceof TestCase) { + /** @phpstan-ignore-next-line */ + ParallelTesting::callSetUpTestCaseCallbacks($this); + } + } + + /** + * Teardown parallel testing callback. + */ + protected function tearDownParallelTestingCallbacks(): void + { + if (class_exists(ParallelTesting::class) && $this instanceof TestCase) { + /** @phpstan-ignore-next-line */ + ParallelTesting::callTearDownTestCaseCallbacks($this); + } + } + + /** + * Register a callback to be run after the application is refreshed. + * + * @param callable():void $callback + * @return void + */ + protected function afterApplicationRefreshed(callable $callback): void + { + $this->afterApplicationRefreshedCallbacks[] = $callback; + + if ($this->setUpHasRun) { + \call_user_func($callback); + } + } + + /** + * Execute the application's post-refreshed callbacks. + * + * @return void + */ + protected function callAfterApplicationRefreshedCallbacks(): void + { + foreach ($this->afterApplicationRefreshedCallbacks as $callback) { + \call_user_func($callback); + } + } + + /** + * Register a callback to be run after the application is created. + * + * @param callable():void $callback + * @return void + */ + protected function afterApplicationCreated(callable $callback): void + { + $this->afterApplicationCreatedCallbacks[] = $callback; + + if ($this->setUpHasRun) { + \call_user_func($callback); + } + } + + /** + * Execute the application's post-creation callbacks. + * + * @return void + */ + protected function callAfterApplicationCreatedCallbacks(): void + { + foreach ($this->afterApplicationCreatedCallbacks as $callback) { + \call_user_func($callback); + } + } + + /** + * Register a callback to be run before the application is destroyed. + * + * @param callable():void $callback + * @return void + */ + protected function beforeApplicationDestroyed(callable $callback): void + { + array_unshift($this->beforeApplicationDestroyedCallbacks, $callback); + } + + /** + * Execute the application's pre-destruction callbacks. + * + * @return void + */ + protected function callBeforeApplicationDestroyedCallbacks(): void + { + foreach ($this->beforeApplicationDestroyedCallbacks as $callback) { + try { + \call_user_func($callback); + } catch (Throwable $e) { + if (! $this->callbackException) { + $this->callbackException = $e; + } + } + } + } + + /** + * Refresh the application instance. + * + * @return void + */ + abstract protected function refreshApplication(); +} diff --git a/src/Concerns/Testing.php b/src/Concerns/Testing.php index 93f373cd8..884c3e842 100644 --- a/src/Concerns/Testing.php +++ b/src/Concerns/Testing.php @@ -2,11 +2,6 @@ namespace Orchestra\Testbench\Concerns; -use Carbon\Carbon; -use Carbon\CarbonImmutable; -use Illuminate\Console\Application as Artisan; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Bootstrap\HandleExceptions; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTruncation; @@ -14,16 +9,11 @@ use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Foundation\Testing\WithoutEvents; use Illuminate\Foundation\Testing\WithoutMiddleware; -use Illuminate\Queue\Queue; -use Illuminate\Support\Facades\ParallelTesting; use Illuminate\Support\LazyCollection; -use Illuminate\View\Component; -use Mockery; -use PHPUnit\Framework\TestCase; -use Throwable; trait Testing { + use ApplicationTestingHooks; use CreatesApplication; use HandlesAnnotations; use HandlesAttributes; @@ -32,41 +22,6 @@ trait Testing use InteractsWithMigrations; use WithFactories; - /** - * The Illuminate application instance. - * - * @var \Illuminate\Foundation\Application|null - */ - protected $app; - - /** - * The callbacks that should be run after the application is created. - * - * @var array - */ - protected $afterApplicationCreatedCallbacks = []; - - /** - * The callbacks that should be run after the application is refreshed. - * - * @var array - */ - protected $afterApplicationRefreshedCallbacks = []; - - /** - * The callbacks that should be run before the application is destroyed. - * - * @var array - */ - protected $beforeApplicationDestroyedCallbacks = []; - - /** - * The exception thrown while running an application destruction callback. - * - * @var \Throwable|null - */ - protected $callbackException; - /** * Indicates if we have made it through the base setUp function. * @@ -83,26 +38,9 @@ trait Testing */ final protected function setUpTheTestEnvironment(): void { - if (! $this->app) { - $this->refreshApplication(); - - $this->setUpParallelTestingCallbacks(); - } - - /** @var \Illuminate\Foundation\Application $app */ - $app = $this->app; - - foreach ($this->afterApplicationRefreshedCallbacks as $callback) { - \call_user_func($callback); - } - - $this->setUpTraits(); - - foreach ($this->afterApplicationCreatedCallbacks as $callback) { - \call_user_func($callback); - } - - Model::setEventDispatcher($app['events']); + $this->setUpTheApplicationTestingHooks(function () { + $this->setUpTraits(); + }); $this->setUpHasRun = true; } @@ -116,61 +54,25 @@ final protected function setUpTheTestEnvironment(): void */ final protected function tearDownTheTestEnvironment(): void { - if ($this->app) { - $this->callBeforeApplicationDestroyedCallbacks(); - - $this->tearDownParallelTestingCallbacks(); - - $this->app?->flush(); - - $this->app = null; - } + $this->tearDownTheApplicationTestingHooks(function () { + $this->setUpHasRun = false; - $this->setUpHasRun = false; - - if (property_exists($this, 'serverVariables')) { - $this->serverVariables = []; - } - - if (property_exists($this, 'defaultHeaders')) { - $this->defaultHeaders = []; - } - - if (class_exists(Mockery::class)) { - if ($container = Mockery::getContainer()) { - $this->addToAssertionCount($container->mockery_getExpectationCount()); + if (property_exists($this, 'serverVariables')) { + $this->serverVariables = []; } - Mockery::close(); - } - - Carbon::setTestNow(); - - if (class_exists(CarbonImmutable::class)) { - CarbonImmutable::setTestNow(); - } - - $this->afterApplicationCreatedCallbacks = []; - $this->beforeApplicationDestroyedCallbacks = []; - - if (property_exists($this, 'originalExceptionHandler')) { - $this->originalExceptionHandler = null; - } - - if (property_exists($this, 'originalDeprecationHandler')) { - $this->originalDeprecationHandler = null; - } + if (property_exists($this, 'defaultHeaders')) { + $this->defaultHeaders = []; + } - Artisan::forgetBootstrappers(); - Component::flushCache(); - Component::forgetComponentsResolver(); - Component::forgetFactory(); - Queue::createPayloadUsing(null); - HandleExceptions::forgetApp(); + if (property_exists($this, 'originalExceptionHandler')) { + $this->originalExceptionHandler = null; + } - if ($this->callbackException) { - throw $this->callbackException; - } + if (property_exists($this, 'originalDeprecationHandler')) { + $this->originalDeprecationHandler = null; + } + }); } /** @@ -263,87 +165,6 @@ protected function setUpTheTestEnvironmentTraitToBeIgnored(string $use): bool return false; } - /** - * Setup parallel testing callback. - */ - protected function setUpParallelTestingCallbacks(): void - { - if (class_exists(ParallelTesting::class) && $this instanceof TestCase) { - /** @phpstan-ignore-next-line */ - ParallelTesting::callSetUpTestCaseCallbacks($this); - } - } - - /** - * Teardown parallel testing callback. - */ - protected function tearDownParallelTestingCallbacks(): void - { - if (class_exists(ParallelTesting::class) && $this instanceof TestCase) { - /** @phpstan-ignore-next-line */ - ParallelTesting::callTearDownTestCaseCallbacks($this); - } - } - - /** - * Register a callback to be run after the application is refreshed. - * - * @param callable():void $callback - * @return void - */ - protected function afterApplicationRefreshed(callable $callback): void - { - $this->afterApplicationRefreshedCallbacks[] = $callback; - - if ($this->setUpHasRun) { - \call_user_func($callback); - } - } - - /** - * Register a callback to be run after the application is created. - * - * @param callable():void $callback - * @return void - */ - protected function afterApplicationCreated(callable $callback): void - { - $this->afterApplicationCreatedCallbacks[] = $callback; - - if ($this->setUpHasRun) { - \call_user_func($callback); - } - } - - /** - * Register a callback to be run before the application is destroyed. - * - * @param callable():void $callback - * @return void - */ - protected function beforeApplicationDestroyed(callable $callback): void - { - array_unshift($this->beforeApplicationDestroyedCallbacks, $callback); - } - - /** - * Execute the application's pre-destruction callbacks. - * - * @return void - */ - protected function callBeforeApplicationDestroyedCallbacks() - { - foreach ($this->beforeApplicationDestroyedCallbacks as $callback) { - try { - \call_user_func($callback); - } catch (Throwable $e) { - if (! $this->callbackException) { - $this->callbackException = $e; - } - } - } - } - /** * Reload the application instance with cached routes. */ @@ -359,11 +180,4 @@ protected function reloadApplication(): void * @return array */ abstract protected function setUpTraits(); - - /** - * Refresh the application instance. - * - * @return void - */ - abstract protected function refreshApplication(); } diff --git a/src/TestCase.php b/src/TestCase.php index 5db75dac0..bb770924e 100644 --- a/src/TestCase.php +++ b/src/TestCase.php @@ -94,6 +94,7 @@ protected function setUpTheTestEnvironmentTraitToBeIgnored(string $use): bool Testing\Concerns\InteractsWithSession::class, Testing\Concerns\InteractsWithTime::class, Testing\Concerns\MakesHttpRequests::class, + Concerns\ApplicationTestingHooks::class, Concerns\CreatesApplication::class, Concerns\Database\HandlesConnections::class, Concerns\HandlesAnnotations::class,