-
-
Save slava-vishnyakov/2ae1d1e030204ad10c1898b20aeba4cb to your computer and use it in GitHub Desktop.
A Better Database Testing Workflow in Laravel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Illuminate\Contracts\Console\Kernel; | |
trait DatabaseSetup | |
{ | |
protected static $migrated = false; | |
public function setupDatabase() | |
{ | |
if ($this->isInMemory()) { | |
$this->setupInMemoryDatabase(); | |
} else { | |
$this->setupTestDatabase(); | |
} | |
} | |
protected function isInMemory() | |
{ | |
return config('database.connections')[config('database.default')]['database'] == ':memory:'; | |
} | |
protected function setupInMemoryDatabase() | |
{ | |
$this->artisan('migrate'); | |
$this->app[Kernel::class]->setArtisan(null); | |
} | |
protected function setupTestDatabase() | |
{ | |
if (!static::$migrated) { | |
$this->whenMigrationsChange(function() { | |
$this->artisan('migrate:refresh'); | |
$this->app[Kernel::class]->setArtisan(null); | |
}); | |
static::$migrated = true; | |
} | |
$this->beginDatabaseTransaction(); | |
} | |
public function beginDatabaseTransaction() | |
{ | |
$database = $this->app->make('db'); | |
foreach ($this->connectionsToTransact() as $name) { | |
$database->connection($name)->beginTransaction(); | |
} | |
$this->beforeApplicationDestroyed(function () use ($database) { | |
foreach ($this->connectionsToTransact() as $name) { | |
$database->connection($name)->rollBack(); | |
} | |
}); | |
} | |
protected function connectionsToTransact() | |
{ | |
return property_exists($this, 'connectionsToTransact') | |
? $this->connectionsToTransact : [null]; | |
} | |
protected function getMigrationsMd5() | |
{ | |
return md5(collect(glob(base_path('database/migrations/*'))) | |
->map(function ($f) { | |
return file_get_contents($f); | |
})->implode('')); | |
} | |
protected function whenMigrationsChange($callback) | |
{ | |
$md5 = $this->getMigrationsMd5(); | |
$path = storage_path('app/migrations_md5.txt'); | |
if(!file_exists($path) || ($md5 !== file_get_contents($path))) { | |
$callback(); | |
file_put_contents($path, $md5); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
abstract class TestCase extends Illuminate\Foundation\Testing\TestCase | |
{ | |
use DatabaseSetup; | |
/** | |
* The base URL to use while testing the application. | |
* | |
* @var string | |
*/ | |
protected $baseUrl = 'http://localhost'; | |
protected function setUp() | |
{ | |
parent::setUp(); | |
$this->setupDatabase(); | |
} | |
/** | |
* Creates the application. | |
* | |
* @return \Illuminate\Foundation\Application | |
*/ | |
public function createApplication() | |
{ | |
$app = require __DIR__.'/../bootstrap/app.php'; | |
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); | |
return $app; | |
} | |
} |
Very cool.
For slightly larger codebases with lots of migrations, this getMigrationsMd5()
function should be a tiny bit faster.
It uses the file mtimes instead of the file contents. Assuming the mtime changes along with the contents, the effect should be the same without having the extra I/O of reading each file.
protected function getMigrationsMd5()
{
return md5(
collect(glob(base_path('database/migrations/*')))
->map(function ($f) {
return $f . filemtime($f);
})
->implode('')
);
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for this