Blog

Talking about us
Go back to Blog home

Fresh Database Once Before Testing Starts

Laravel PHPUnit — Run tests automatically as pre-commit hook

Stefan Dreßler May 23, 2019

This article shows how to migrate a fresh database once before testing starts and how to run tests automatically before every git commit in a separate testing database. Based on Laravel 5.7



“The best tests are the ones that are running frequently, fast and automatically. They should run in a clean and separate testing database and should prevent us from committing untested code into our git repository.”

Laravel comes with the trait „RefreshDatabase“. This trait is build to refresh the database before each test. On large databases, this will slow down testing significantly. In our case, we want a fresh database once before we start our testing. So let’s create a trait which runs php artisan migrate:fresh --seed once before testing starts.

Following should be already in place:

  1. Laravel Framework (article based on v5.7)
  2. PHPUnit is running (article based on v7.3.5)
3. You have build up Laravels Migrations, Factories and Seeds.

3. You have build up Laravels Migrations, Factories and Seeds.

4. Your tests are extending TestCase.php from the tests directory.

4. Your tests are extending TestCase.php from the tests directory.

You should get green running the following commands:

php artisan migrate:fresh --seed
phpunit

Remove „RefreshDatabase“

Laravel ships with this trait, but it is build to refresh the database before each test. So, if you played around with this trait, make sure you removed it from your general TestCase.php.

Lets Create “MigrateFreshSeedOnce”

Create a trait named „MigrateFreshSeedOnce.php“ in your tests folder.

Create a trait named „MigrateFreshSeedOnce.php“ in your tests folder.

Code for the trait:

You can easily edit the artisan commands as you like.

<?php
namespace Tests;
use Illuminate\Support\Facades\Artisan;
trait MigrateFreshSeedOnce
{
    /**
    * If true, setup has run at least once.
    * @var boolean
    */
    protected static $setUpHasRunOnce = false;
    /**
    * After the first run of setUp "migrate:fresh --seed"
    * @return void
    */
    public function setUp()
    {
        parent::setUp();
        if (!static::$setUpHasRunOnce) {
            Artisan::call('migrate:fresh');
            Artisan::call(
                'db:seed', ['--class' => 'DatabaseSeeder']
            );
            static::$setUpHasRunOnce = true;
         }
    }
}

Use this new trait in your tests/TestCase.php

...
abstract class TestCase extends BaseTestCase
{
    use CreatesApplication,
    MigrateFreshSeedOnce;
...

Done!

Running phpunit will now run php artisan migrate:fresh --seed once before your testing starts. In our case, feature tests run 98% faster with this trait. Try phpunit --testdox for nicely readable results.

Use Separate Testing Database

Feature tests should always run in a separate testing database. So create your test database and make sure your existing database user has all privileges for this new database. Then find your phpunit.xml and edit the „php“ block at the end.

You can overwrite all env variables set in your “.env” file.

You can overwrite all env variables set in your “.env” file.

Never Commit Untested Code Again. Automate!

To run tests frequently and to keep our repository clean of untested code, we decided to run PHPUnit automatically by a pre-commit hook. If a test fails, the git commit will fail two. So now its proofed: “Committed code is tested code.”

Just create a file named „pre-commit“ in your local git hooks directory and add this code. vim .git/hooks/pre-commit

#!/usr/bin/env php
<?php
echo "Running tests.. ";
exec('vendor/bin/phpunit', $output, $returnCode);
if ($returnCode !== 0) {
    // Show full output
    echo PHP_EOL . implode($output, PHP_EOL) . PHP_EOL;
    echo "Aborting commit.." . PHP_EOL;
    exit(1);
}
// Show summary (last line)
echo array_pop($output) . PHP_EOL;
exit(0);

After all

If your tests still consume too much time for a pre-commit hook, try to simplify them, or try to run parts of them. Maybe your UnitTests only:

exec('vendor/bin/phpuni tests/Unit', $output, $returnCode)

That’s it!

I hope you enjoyed reading this article. Please let me know if you have any questions or suggestions.


Let's keep you in the loop of new awesome articles.

Subscribe to our newsletter and we will keep you posted about new articles.

Keep me posted
Wave on the bottom