Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows Setup and Teardown actions to be reused in alternative TestCase for Laravel #49973

Merged
merged 3 commits into from Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,282 @@
<?php

namespace Illuminate\Foundation\Testing\Concerns;

use Carbon\CarbonImmutable;
use Illuminate\Console\Application as Artisan;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Bootstrap\HandleExceptions;
use Illuminate\Foundation\Console\AboutCommand;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Foundation\Http\Middleware\TrimStrings;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\DatabaseTruncation;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\WithoutEvents;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Queue\Queue;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\Facades\ParallelTesting;
use Illuminate\Support\Sleep;
use Illuminate\Support\Str;
use Illuminate\View\Component;
use Mockery;
use Mockery\Exception\InvalidCountException;

trait InteractsWithTestCaseLifecycle
{
/**
* The Illuminate application instance.
*
* @var \Illuminate\Foundation\Application
*/
protected $app;

/**
* The callbacks that should be run after the application is created.
*
* @var array
*/
protected $afterApplicationCreatedCallbacks = [];

/**
* 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
*/
protected $callbackException;

/**
* Indicates if we have made it through the base setUp function.
*
* @var bool
*/
protected $setUpHasRun = false;

/**
* Setup the test environment.
*
* @internal
*
* @return void
*/
protected function setUpTheTestEnvironment(): void
{
Facade::clearResolvedInstances();

if (! $this->app) {
$this->refreshApplication();

ParallelTesting::callSetUpTestCaseCallbacks($this);
}

$this->setUpTraits();

foreach ($this->afterApplicationCreatedCallbacks as $callback) {
$callback();
}

Model::setEventDispatcher($this->app['events']);

$this->setUpHasRun = true;
}

/**
* Clean up the testing environment before the next test.
*
* @internal
*
* @return void
*/
protected function tearDownTheTestEnvironment(): void
{
if ($this->app) {
$this->callBeforeApplicationDestroyedCallbacks();

ParallelTesting::callTearDownTestCaseCallbacks($this);

$this->app->flush();

$this->app = null;
}

$this->setUpHasRun = false;

if (property_exists($this, 'serverVariables')) {
$this->serverVariables = [];
}

if (property_exists($this, 'defaultHeaders')) {
$this->defaultHeaders = [];
}

if (class_exists('Mockery')) {
if ($container = Mockery::getContainer()) {
$this->addToAssertionCount($container->mockery_getExpectationCount());
}

try {
Mockery::close();
} catch (InvalidCountException $e) {
if (! Str::contains($e->getMethodName(), ['doWrite', 'askQuestion'])) {
throw $e;
}
}
}

if (class_exists(Carbon::class)) {
Carbon::setTestNow();
}

if (class_exists(CarbonImmutable::class)) {
CarbonImmutable::setTestNow();
}

$this->afterApplicationCreatedCallbacks = [];
$this->beforeApplicationDestroyedCallbacks = [];

$this->originalExceptionHandler = null;
$this->originalDeprecationHandler = null;

AboutCommand::flushState();
Artisan::forgetBootstrappers();
Component::flushCache();
Component::forgetComponentsResolver();
Component::forgetFactory();
ConvertEmptyStringsToNull::flushState();
HandleExceptions::forgetApp();
Queue::createPayloadUsing(null);
Sleep::fake(false);
TrimStrings::flushState();

if ($this->callbackException) {
throw $this->callbackException;
}
}

/**
* Boot the testing helper traits.
*
* @return array
*/
protected function setUpTraits()
{
$uses = array_flip(class_uses_recursive(static::class));

if (isset($uses[RefreshDatabase::class])) {
$this->refreshDatabase();
}

if (isset($uses[DatabaseMigrations::class])) {
$this->runDatabaseMigrations();
}

if (isset($uses[DatabaseTruncation::class])) {
$this->truncateDatabaseTables();
}

if (isset($uses[DatabaseTransactions::class])) {
$this->beginDatabaseTransaction();
}

if (isset($uses[WithoutMiddleware::class])) {
$this->disableMiddlewareForAllTests();
}

if (isset($uses[WithoutEvents::class])) {
$this->disableEventsForAllTests();
}

if (isset($uses[WithFaker::class])) {
$this->setUpFaker();
}

foreach ($uses as $trait) {
if (method_exists($this, $method = 'setUp'.class_basename($trait))) {
$this->{$method}();
}

if (method_exists($this, $method = 'tearDown'.class_basename($trait))) {
$this->beforeApplicationDestroyed(fn () => $this->{$method}());
}
}

return $uses;
}

/**
* Clean up the testing environment before the next test case.
*
* @internal
*
* @return void
*/
public static function tearDownAfterClassUsingTestCase()
{
foreach ([
\PHPUnit\Util\Annotation\Registry::class,
\PHPUnit\Metadata\Annotation\Parser\Registry::class,
] as $class) {
if (class_exists($class)) {
(function () {
$this->classDocBlocks = [];
$this->methodDocBlocks = [];
})->call($class::getInstance());
}
}
}

/**
* Register a callback to be run after the application is created.
*
* @param callable $callback
* @return void
*/
public function afterApplicationCreated(callable $callback)
{
$this->afterApplicationCreatedCallbacks[] = $callback;

if ($this->setUpHasRun) {
$callback();
}
}

/**
* Register a callback to be run before the application is destroyed.
*
* @param callable $callback
* @return void
*/
protected function beforeApplicationDestroyed(callable $callback)
{
$this->beforeApplicationDestroyedCallbacks[] = $callback;
}

/**
* Execute the application's pre-destruction callbacks.
*
* @return void
*/
protected function callBeforeApplicationDestroyedCallbacks()
{
foreach ($this->beforeApplicationDestroyedCallbacks as $callback) {
try {
$callback();
} catch (Throwable $e) {
if (! $this->callbackException) {
$this->callbackException = $e;
}
}
}
}
}