Skip to content

Instantly share code, notes, and snippets.

@Alymosul
Last active February 22, 2024 14:45
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • 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();
}
}
@georgewritescode
Copy link

georgewritescode commented Feb 3, 2018

Hi,
Thank you for sharing. Is this code licensed under the MIT license as below? I'd like to use it for a commercial project.

MIT License:

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@Alymosul
Copy link
Author

@georgewritescode yes, feel free to use it.

@samoldenburg
Copy link

Thank you for this, I had started going down a similar path and ran into this, and it worked right out of the box to dramatically speed up tests for an application with a big seeding process.

@pardmeister
Copy link

@Alymosul, Thanks for the gist. I am having problems using it with different database types, and I was wondering whether you can assist. When using sqlite, in memory database, I need to set seed once to false, and when using mysql to true. Otherwise my tests fail on one or the other. Do you have an idea why this could be happening?

@rossysa
Copy link

rossysa commented Mar 8, 2019

Thank you so much for this! I was going a bit crazy trying to seed my database before running the tests and I am so happy I found your solution. It has saved me so much time and pain. Again, thank you for sharing!

@Alymosul
Copy link
Author

@pardmeister Could you share with me the error?

@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