troccoli's avatar

How to test for non-existing database

I have a command that runs the migrate:fresh command on some databases, using their own migrations.

I also have a test for when the database is missing

it('returns an error if it cannot migrate to the database', function (): void {
    config()->set('database.connections.sgr.testing', [
        'driver' => 'sqlite',
        'database' => database_path('nonexistent.sqlite'),
    ]);
    $this->artisan('sgr:migrate-legacy', ['databases' => ['sgr']])
        ->expectsOutput('Cannot migrate the \'sgr\' database.')
        ->assertFailed();
});

In Laravel 11.36.1 the test passed. In Laravel 11.40.0 it doesn't, throwing a SQLiteDatabaseDoesNotExistException.

After some investigation this is due to a change in the RefreshDatabase trait

111,115d110
< 
<                 if ($connection->getPdo() && ! $connection->getPdo()->inTransaction()) {
<                     RefreshDatabaseState::$migrated = false;
<                 }
< 

See this commit and this commit

This is part of its closure for the beforeApplicationDestroyed() method. So basically my test still passes (I confirm it by debugging into it), but then the RefreshDatabase trait tries to get the PDO during the tear down process.

Does anybody have any suggestions on how I can resolve this? I would like to keep my test, as it is still correct after all.

0 likes
2 replies
LaryAI's avatar
Level 58

To address the issue you're encountering with the SQLiteDatabaseDoesNotExistException in your test, you can modify your approach to handle the exception gracefully during the test teardown process. Since the problem arises from the RefreshDatabase trait trying to access a non-existent database, you can catch the exception and prevent it from causing the test to fail.

Here's a potential solution:

  1. Custom Exception Handling: You can create a custom exception handler for your test to catch the SQLiteDatabaseDoesNotExistException and handle it appropriately.

  2. Modify the Test: Adjust your test to ensure that it doesn't fail due to the exception during the teardown process.

Here's how you can implement this:

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\SQLiteDatabaseDoesNotExistException;

it('returns an error if it cannot migrate to the database', function (): void {
    config()->set('database.connections.sgr.testing', [
        'driver' => 'sqlite',
        'database' => database_path('nonexistent.sqlite'),
    ]);

    try {
        $this->artisan('sgr:migrate-legacy', ['databases' => ['sgr']])
            ->expectsOutput('Cannot migrate the \'sgr\' database.')
            ->assertFailed();
    } catch (SQLiteDatabaseDoesNotExistException $e) {
        // Handle the exception if needed, or simply ignore it
    }
})->after(function () {
    // Ensure that the database connection is reset or closed properly
    try {
        DB::connection('sgr.testing')->disconnect();
    } catch (SQLiteDatabaseDoesNotExistException $e) {
        // Ignore the exception during teardown
    }
});

Explanation:

  • Try-Catch Block: The test is wrapped in a try-catch block to catch the SQLiteDatabaseDoesNotExistException during the test execution. This ensures that the test logic can proceed without being interrupted by the exception.

  • Teardown Handling: The after method is used to handle any necessary cleanup after the test. Here, you attempt to disconnect the database connection, catching and ignoring the exception if it occurs.

This approach allows you to maintain your test logic while preventing the exception from causing the test to fail during the teardown process.

troccoli's avatar

Well, for once @laryai was useful.

Wrapping the call in a try {} catch() {} is useless, as the exception is not thrown by my code. But the after() is just what I needed.

Please or to participate in this conversation.