Skip to content

Instantly share code, notes, and snippets.

@Alymosul
Last active May 9, 2024 13:37
Show Gist options
  • Save Alymosul/c38504126f92b6c054164ca061c397a3 to your computer and use it in GitHub Desktop.
Save Alymosul/c38504126f92b6c054164ca061c397a3 to your computer and use it in GitHub Desktop.
[Laravel] Seeding data in testing as part of the application build.

SeedDatabase trait along with SeedDatabaseState class gives your Laravel project the ability to seed the testing database once before running the full suite tests, which improves the speed of the tests than seeding the testing database before each test.

Also, it has the option to run custom seeders instead of the seeders that are called in the run() method of the DatabaseSeeder class you can achieve that as follows

...in the Testcase.php

public function setUp()
{
    parent::setUp();
    SeedDatabaseState::$seeders = [RolesSeeder::class, PermissionSeeder::class];
    $this->seedDatabase();
}

Instructions:

  • Place the SeedDatabase.php and the SeedDatabaseState.php into the tests folder in your Laravel project and update the TestCase.php to use the SeedDatabase trait and make the changes in the setup() method to call $this->seedDatabase(); after calling the parent::setup()
<?php
namespace Tests;
use Illuminate\Database\Seeder;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\RefreshDatabase;
trait SeedDatabase
{
/**
* Seeds the database.
*
* @return void
*/
public function seedDatabase()
{
if (! SeedDatabaseState::$seeded) {
$this->runSeeders(SeedDatabaseState::$seeders);
$this->syncTransactionTraits();
if (SeedDatabaseState::$seedOnce) {
SeedDatabaseState::$seeded = true;
}
}
}
/**
* Calls specific seeders if possible.
*
* @param array $seeders
*/
public function runSeeders(array $seeders)
{
if (empty($seeders)) {
$this->artisan('db:seed');
$this->app[Kernel::class]->setArtisan(null);
return;
}
$this->getSeederInstance()->call($seeders);
}
/**
* Persists the seed and begins a new transaction
* where the rollback has been already registered in Transaction traits.
*
* @return void
*/
public function syncTransactionTraits()
{
$uses = array_flip(class_uses_recursive(static::class));
if (isset($uses[RefreshDatabase::class]) || isset($uses[DatabaseTransactions::class])) {
$database = $this->app->make('db');
foreach ($this->connectionsToTransact() as $name) {
$database->connection($name)->commit();
$database->connection($name)->beginTransaction();
}
}
}
/**
* Builds a quick seeder instance.
*
* @return Seeder
*/
private function getSeederInstance()
{
return
new class() extends Seeder {
public function run()
{
}
};
}
}
<?php
namespace Tests;
class SeedDatabaseState
{
/**
* Indicates if the test database has been seeded.
*
* @var bool
*/
public static $seeded = false;
/**
* Indicates if the seeders should run once at the beginning of the suite.
*
* @var bool
*/
public static $seedOnce = true;
/**
* Runs only these registered seeders instead of running all seeders.
*
* @var array
*/
public static $seeders = [];
}
<?php
namespace Tests;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, RefreshDatabase, SeedDatabase;
public function setUp()
{
parent::setUp();
$this->seedDatabase();
}
}
@georgebohnisch
Copy link

Thanks for this

@MarceloHoffmeister
Copy link

MarceloHoffmeister commented Sep 3, 2020

Help me a lot. Thanks!
But the inserts don't rollback. Is that normal?

@Dayjo
Copy link

Dayjo commented Nov 5, 2020

@Alymosul Thanks for this, it's really helpful. I'm having a weird issue where between tests the database is emptied. I have two test functions in one file essentially doing the same thing, the first one manages to pull from the seed data fine, but the second (trying to get the same row from the db) cannot find the data.

Anyone have any thoughts?

I am unfortunately on Laravel 5.5

@Findarato
Copy link

@Alymosul Thanks for this, it's really helpful. I'm having a weird issue where between tests the database is emptied. I have two test functions in one file essentially doing the same thing, the first one manages to pull from the seed data fine, but the second (trying to get the same row from the db) cannot find the data.

Anyone have any thoughts?

I am unfortunately on Laravel 5.5

I am having the same issue. Are you using in memory databases? I am on laravel 8

@MarceloHoffmeister
Copy link

@Alymosul Thanks for this, it's really helpful. I'm having a weird issue where between tests the database is emptied. I have two test functions in one file essentially doing the same thing, the first one manages to pull from the seed data fine, but the second (trying to get the same row from the db) cannot find the data.

Anyone have any thoughts?

I am unfortunately on Laravel 5.5

@Alymosul Thanks for this, it's really helpful. I'm having a weird issue where between tests the database is emptied. I have two test functions in one file essentially doing the same thing, the first one manages to pull from the seed data fine, but the second (trying to get the same row from the db) cannot find the data.
Anyone have any thoughts?
I am unfortunately on Laravel 5.5

I am having the same issue. Are you using in memory databases? I am on laravel 8

I used this code in my project during a time, but it drop database on each test yet, or i don't how use it, but doesn't work for me. Now i'm just using DatabaseTransactions trait and avoid use many relactions creation on factories. This make my tests usefull an more fast.

@irvadesigner
Copy link

Help me a lot. Thanks so much!

@jackkitley
Copy link

is this still used. even for Laravel 9?

@takagaki-bb
Copy link

@Alymosul Thanks for this, it's really helpful. I'm having a weird issue where between tests the database is emptied. I have two test functions in one file essentially doing the same thing, the first one manages to pull from the seed data fine, but the second (trying to get the same row from the db) cannot find the data.

Anyone have any thoughts?

I am unfortunately on Laravel 5.5

I found a problem with this trait.
When this trait is run on a class that is not using RefreshDatabase or DatabaseTransactions, it is marked as already seeded and will not be seeded again.
In code, I think it is correct to modify seedDatabase as follows

    public function seedDatabase()
    {
        if (!SeedDatabaseState::$seeded) {
            $uses = array_flip(class_uses_recursive(static::class));
            if (isset($uses[RefreshDatabase::class]) || isset($uses[DatabaseTransactions::class])) {
                $this->runSeeders(SeedDatabaseState::$seeders);

                $this->syncTransactionTraits();

                if (SeedDatabaseState::$seedOnce) {
                    SeedDatabaseState::$seeded = true;
                }
            }
        }
    }

Conversely, syncTransactionTraits are simpler.

    public function syncTransactionTraits()
    {
        $database = $this->app->make('db');

        foreach ($this->connectionsToTransact() as $name) {
            $database->connection($name)->commit();
            $database->connection($name)->beginTransaction();
        }
    }

This worked in my environment.

@h-sigma
Copy link

h-sigma commented Oct 24, 2023

You're a hero ^^

@mahdi375
Copy link

🚀 Saved my day :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment