'Retaining a sqlite database for multiple tests in Laravel/phpunit to reduce time taken
I have a lot of tests and running all of them takes a long time ~15 minutes. This is mainly due to a lot fo the tests building a new sqlite database and seeding it.
A lot of my tests don't change the database, so they could all be run on the same database, which is created just once. However, I can't figure out how to setup my tests to work like this.
I use an in memory sqlite, in Laravel.
I am trying to stop my phpunit tests from creating and seeding the database every time.
My latest attempt is to use the trait detailed here: https://stackoverflow.com/a/57788123/42106
However, when I run my tests, the first test passes fine (so the database tables exist) then the 2nd test in the file fails with: "General error: 1 no such table: users".
./bin/phpunit ./tests/Auth/UserTest.php
So the database tables have been wiped after the first test.
I have tried overriding the tearDown method but it makes no difference.
What could be wiping my database?
<?php
namespace Tests\Auth;
use Tests\TestCase;
use Tests\MigrateFreshAndSeedOnce;
use App\Entity\Models\User;
class UserTest extends TestCase
{
use MigrateFreshAndSeedOnce;
public function testUser1()
{
$user = User::where('id', 1)->get()->first();
$this->assertTrue($user->id !== null);
}
public function testUser2()
{
$user = User::where('id', 2)->get()->first();
$this->assertTrue($user->id !== null);
}
}
Here is the trait:
<?php
namespace Tests;
use Illuminate\Support\Facades\Artisan;
trait MigrateFreshAndSeedOnce
{
/**
* If true, setup has run at least once.
*
* @var boolean
*/
protected static $setUpHasRunOnce = false;
/**
*
* @return void
*/
public function setUp() : void
{
parent::setUp();
if (!static::$setUpHasRunOnce) {
echo '--DB--';
Artisan::call('migrate:fresh');
Artisan::call(
'db:seed',
['--class' => 'DatabaseSeeder'] //add your seed class
);
static::$setUpHasRunOnce = true;
}
}
}
Finally, my TestCase class:
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
protected $baseUrl = 'http://dev.php73.mysite.com:8888';
}
My phpunit env vars:
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
<env name="DB_CONNECTION" value="testing" />
</php>
Thanks!
Solution 1:[1]
as @matiaslauriti stated in the comments, with Laravel 8, you should use the RefreshDatabase trait. Once applied it will look for a boolean property called seed.
Give your test class the protected property $seed and set it to true if you wish to seed and move tests that don't need a refresh to another test class.
class ProjectControllerTest extends TestCase
{
protected $seed = true;
}
Then only seed necessary tests will build the database.
Alternatively use:
$this->seed();
inside a test method, to seed for the specific test.
check out the docs on it
Solution 2:[2]
You're almost there believe it or not...
Change the name of the setUp() method in your MigrateFreshAndSeedOnce trait to setUpOnce() like so...
<?php
namespace Tests;
use Illuminate\Support\Facades\Artisan;
trait MigrateFreshAndSeedOnce
{
/**
* If true, setup has run at least once.
*
* @var boolean
*/
protected static $setUpHasRunOnce = false;
/**
*
* @return void
*/
public function setUpOnce() : void
{
parent::setUp();
if (!static::$setUpHasRunOnce) {
You just need to implement the setUp method on each test case class (which does the magic with the trait) like so...
<?php
namespace Tests\Auth;
use Tests\TestCase;
use Tests\MigrateFreshAndSeedOnce;
use App\Entity\Models\User;
class UserTest extends TestCase
{
use MigrateFreshAndSeedOnce;
protected function setUp(): void
{
$this->setUpOnce();
}
public function testUser1()
{
$user = User::where('id', 1)->get()->first();
$this->assertTrue($user->id !== null);
}
public function testUser2()
{
$user = User::where('id', 2)->get()->first();
$this->assertTrue($user->id !== null);
}
}
If you don't want to add that manually to every test case class, perhaps you could add it to your abstract class TestCase and it might apply automagically to all test case classes?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 |
