ashler2's avatar

Testing - SQLite vs MYSQl

Currently i've got a project and to get some testing started i've had to use MYSQL, this is becasue one table uses Enums. Becuase of this i can't use SQLite as you end up with the error General error: 1 table sqlite_master may not be modified (SQL: delete from sqlite_master where type in ('table', 'index', 'trigger'))

I know i can swap this out to use another table with foreign keys instead of the ENUM. But is there a way to use sqlite with an ENUMs column? Also I presume keeping this in memory might also make the tests faster as at the moment i have 21 tests that are taking between 35-40seconds.

0 likes
9 replies
Tray2's avatar
Tray2
Best Answer
Level 74

I would move over to MySQL or MariaDB as the test database.

I'm currently running 339 test with closer to 2000 assertions in just over 10 seconds,

  Tests:  339 passed
  Time:   10.45s

To make that happen I'm using the FastDatabaseRefresh package that you can find here.

https://github.com/PlannrCrm/laravel-fast-refresh-database

I highly recommend it, and I also recommend running your tests against the database server that you will use in production.

3 likes
ashler2's avatar

@Tray2 Just a qucik question - I agree this has its uses and i've forked a version to work with php7.4 and has reduced to 11 seconds, However did you find after installing this package a lot of your tests failed?

With a fair few of my tests now fail as the database is not empty from previous data, for example asserting the customers table is 1 after a create as this has x number of customers from other tests. Is there an easy way around this?

Tray2's avatar

@ashler2 Nope, they all run fine.

The database should be empty of data, but not of the migrated tables.

If you still have data in them then you are doing something wrong.

The most important thing is to never assume that an id will equal 1, always fetch the id and use it.

I do it this way in some of my tests

beforeEach(function () {
    $this->seed(MediaTypeSeeder::class);
    $this->mediaTypeId = MediaType::query()
        ->where('name', 'game')
        ->value('id');
});


it('shows all information about a game', function () {
    $game = Game::factory()->create([
        'format_id' => Format::factory()->create(['media_type_id' => $this->mediaTypeId]),
        'genre_id' => Genre::factory()->create(['media_type_id' => $this->mediaTypeId]),
    ]);
    get(route('games.show', $game))
        ->assertOk()
        ->assertSeeText([
            $game->title,
            $game->released_year,
            $game->format->name,
            $game->genre->name,
            $game->platform,
            $game->blurb
        ]);
});

ashler2's avatar

@Tray2 Yeah my Tests look soemthing like this

/**
     * Test the api returns customers
     *
     * @test
     * @return void
     */
    public function api_gets_customers(): void
    {
        $this->login();
        $customer = Customer::factory()->create();
        $response = $this->getJson('api/customers');
        $response->assertStatus(200);
        $this->assertIsArray($response['customers']);
        $this->assertEquals($customer->toArray(), $response['customers'][0]);
    }

And then a second test of

   /**
     * @test
     */
    public function customer_can_be_created_with_correct_data(): void
    {
        $this->login();
        $response = $this->postJson('/api/customers', ['name' => 'test']);
        $customer = $response['customer'];
        $this->assertEquals('test', $customer['name']);
        $this->assertEquals('test', Customer::query()->first()->name);
    } 

Will run but the Customer::first()->name will always be wrong as the customer from the first test exists in the database. It would appear that running the tests with the version of the package doesn't remove my customers in mysql on my testing database. The value exists in the database and i can see the value is input every time the test is ran.

Tray2's avatar

@ashler2 And you have this in your class?

class YouTestClass extends TestCase
{
    use RefreshDatabase;

And with the FastRefresh

class DeleteTest extends TestCase
{
    use FastRefreshDatabase;
ashler2's avatar

@Tray2 Yep, It seems to persist the data. So in my test classes i have

class CustomersTest extends TestCase 

with no use statements. In the TestCase.php

abstract class TestCase extends BaseTestCase
{
   use CreatesApplication;
   use FastRefreshDatabase;

And of course the BaseTestCase is just the basic laravel test case.

I dont beleive there are any settings that exist within laravel that make the test database persist or i have to turn on database transactions anywhere?

ashler2's avatar

I know its not ideal but i've added the following to the fast refresh and its started to work - Not ideal but its reduced from 30seconds to 5 seconds for my tests and they now work as the databse is clear.

This is within the fastRefreshDatabase Trait.

        $this->beginDatabaseTransaction();
        $tableNames = \Illuminate\Support\Facades\Schema::getConnection()->getDoctrineSchemaManager()->listTableNames();
        \Illuminate\Support\Facades\Schema::disableForeignKeyConstraints();
        foreach ($tableNames as $name) {
            //if you don't want to truncate migrations
            if ($name === 'migrations') {
                continue;
            }
            DB::table($name)->truncate();
        }
        \Illuminate\Support\Facades\Schema::enableForeignKeyConstraints();

Tray2's avatar

@ashler2 It should not be in the TestCase class, it should only be in the classes that needs it.

Please or to participate in this conversation.