RefreshDatabase docs states that
The
Illuminate\Foundation\Testing\RefreshDatabase
trait does not migrate your database if your schema is up to date. Instead, it will only execute the test within a database transaction.
I'm on Laravel 10 and I think it is not working properly in my project.
Since the project migrations take ~1m to be executed (php artisan migrate:fresh --env testing
) I have a local testing db that I keep up to date to run tests quickly, but tests execution always took at least 1m as if it is running all the migrations before running the tests.
I need to test that a piece code falls into a catch(Exception $e)
branch and since this piece of code retrieve results from the db I thought to alter the table name after all the setUp
to make it throw an exception, and that worked.
Problem is, now every subsequent test in the same test class that tests the same piece of code fails with the message SQLSTATE[42S02]: Base table or view not found
: this means the table still have the new wrong name, so it looks like it is not the single test to be wrapped in a migration but all the tests in the same test class, but this conflicts with the previous db operation (e.g. instances being created) not affecting subsequent tests.
I really cannot figure out what's not working properly.
I am leaving this as an answer instead of a comment so I can put code and make it more visible:
You are correct about the docs being misleading, have in mind that your original post was linking Laravel 11.x docs instead of 10.x (I have fixed that) but they still say the same thing.
This is the source code for Laravel 10:
public function refreshDatabase()
{
$this->beforeRefreshingDatabase();
$this->usingInMemoryDatabase()
? $this->refreshInMemoryDatabase()
: $this->refreshTestDatabase();
$this->afterRefreshingDatabase();
}
As you can see you have a before
hook, the migration runs, and an after
hook. Both hooks are empty, you can add code in your test to interfere with the trait.
If you check what refreshTestDatabase()
method executes:
protected function refreshTestDatabase()
{
if (! RefreshDatabaseState::$migrated) {
$this->artisan('migrate:fresh', $this->migrateFreshUsing());
$this->app[Kernel::class]->setArtisan(null);
RefreshDatabaseState::$migrated = true;
}
$this->beginDatabaseTransaction();
}
You can see it is making use of RefreshDatabaseState
, it is a simple static class, and it is not set beforehand or manipulated in any way. So RefreshDatabaseState::$migrated
is always false
on the first run, so it will run the code inside it, in this case a migrate:fresh
.
I have never took your approach, but you can try getting in the before
hook and you manually check if you have all migrations run or not, something like this:
// In your test define exactly this
protected function beforeRefreshingDatabase()
{
$this->artisan('migrate');
\Illuminate\Foundation\Testing\RefreshDatabaseState::$migrated = true;
}
This way we are migrating the database automatically (if any run is missing), and we then tell the trait that it was already done, no need to migrate:fresh
. Have in mind that it may not exactly work as it is then running $this->app[Kernel::class]->setArtisan(null);
and I have no idea why and I really don't want to run it.
I thought of using migrate:status
and check if all migrations were run, but it is not giving us a status code if any is missing, so it is harder as you would need to read the output and check it, something not really desired.
The simplified solution is:
migrate
command on a test, just add the command to the setUp
, it can be on the base class or on that specific test\Illuminate\Foundation\Testing\DatabaseTransactions
as RefreshDatabase
, after our code, is exactly using what is inside that other trait, just starting a transactionIf you want this solution, just include DatabaseTransactions
in your test but remember to either automatically run migrate
on setUp
in your base test class or always be up-to-date by CLI, that's it!