Posted on under Laravel by Owen Conti.
Laravel defaults to using an in-memory SQLite database for testing. Using this type of database has some advantages over a traditional MySQL database such as less configuration, your tests "just work", they run faster than a traditional database, etc. However, there are also some disadvantages as well:
RefreshDatabase
trait that comes out of the box with Laravel, a SQLite test suite can actually be much slower than MySQLI'm working on a project where we just transitioned from an in-memory SQLite test suite to a MySQL test suite. At the time of the transition, we had 52 migrations in the project. A full test suite (426 tests) with the in-memory driver took between 1.5 minutes to 3 minutes, depending on the machine running the tests.
After the transition, the full test suite (with more tests too, up to 495 at the time of writing this post), now runs between ~14 seconds to ~20 seconds, depending on the machine running the tests.
1PHPUnit 8.5.3 by Sebastian Bergmann and contributors. 2 3............................................................... 63 / 495 ( 12%) 4............................................................... 126 / 495 ( 25%) 5............................................................... 189 / 495 ( 38%) 6............................................................... 252 / 495 ( 50%) 7............................................................... 315 / 495 ( 63%) 8............................................................... 378 / 495 ( 76%) 9............................................................... 441 / 495 ( 89%)10...................................................... 495 / 495 (100%)11 12Time: 14.49 seconds, Memory: 88.50 MB
The key is in the
RefreshDatabase
trait. When using the trait with an in-memory database, Laravel will re-migrate the database with each test run. So as an example, if your migrations take 1.5 seconds to run, each test using the
RefreshDatabase
trait in your suite will take at least 1.5 seconds. Some basic math for a test suite with 400 tests:
400 * 1.5 = 600 seconds = 10 minutes!!
Ouch.
As fast as in-memory is, the root problem is that you're running migrations over and over again. This is where
RefreshDatabase
shines.
Instead of running your migrations from scratch for each test, the trait keeps track once your migrations have been run for the first time. From that point forward it uses database transactions to rollback the database after each test, which resets the database to the state it was in at the beginning of the test (migrated but with no data).
There is a small caveat that you need to be aware of. When the database rolls back the transaction, any auto-incremented keys are not rolled back. Databases do this to prevent primary key collisions when using transactions.
So if you're asserting that a database includes a hard coded ID, you'll need to update your assertions because you can no longer guarantee that auto-increment fields will start at 1.
We made a simple helper class to use for our tests:
1<?php 2 3namespace Tests\Helpers; 4 5use Illuminate\Support\Facades\DB; 6 7class AutoIncrement 8{ 9 public static function nextId(string $modelClass)10 {11 $model = resolve($modelClass);12 13 if (!$model->getIncrementing()) {14 throw new \Exception("{$model->getTable()} does not use an incrementing key.");15 }16 17 $result = DB::select("SHOW TABLE STATUS LIKE '{$model->getTable()}'");18 return $result[0]->Auto_increment;19 }20}
Usage of this class looks like this:
1<?php 2 3// Before we make our Post, determine the next ID 4$nextId = AutoIncrement::nextId(Post::class); 5 6// Call the endpoint to create a new Post here.. 7 8// Assert the Post was created with the correct ID 9$this->assertDatabaseHas('posts', [10 'id' => $nextId,11 'title' => 'New Post',12]);
There's two steps to switching to using MySQL for your tests:
phpunit.xml
to use the MySQL connection and new database:1<server name="DB_CONNECTION" value="mysql"/>2<server name="DB_DATABASE" value="your_test_database"/>
Apart from those two changes, you'll have to update any tests that assert a hard coded auto-incrementing ID as described above!
Hopefully you found this article useful! If you did, share it on X!
Found an issue with the article? Submit your edits against the repository.