Other Speed Tips

TIP

Do you have any other useful tips to speed up tests? Please Let me know!open in new window.

Use PHPUnit's TestCase

If a test doesn't need any Laravel functionality, extend from PHPUnit\Framework\TestCase instead of Tests\TestCase. It's much quicker.

Don't Use a Database

Not all tests need a database, so skip the database altogether when it's not needed!

This is the easiest optimisation to make, and it might seem obvious. But it's worth mentioning, so it's not overlooked.

Use Memory Databases

There are a few different ways to run your test-databases in memory, depending on the type of database you use.

TIP

When using Adapt, you can use the timing details shown in the logs to get a better idea of what's really fastest for your tests.

SQLite Memory Databases

WARNING

If your project normally uses MySQL or PostgreSQL, you should consider testing against those at least somewhere in your development process.

While it's often possible to run tests with a SQLite database, SQLite isn't fully compatible with MySQL and PostgreSQL.

When using SQLite, you have the option of using a database in memory. Just change the name of the database from a file to :memory:.

# .env.testing

DB_DATABASE=:memory:

However, whilst it might be attractive to do this, it might not be the fastest option.

SQLite memory databases only exist for the current connection, and disappear straight away after disconnection. Because Laravel disconnects from its databases after each test, these databases need to be rebuilt each time.

It depends on your particular tests, but it will probably be quicker to have Adapt reuse file-based SQLite databases.

Instead of using a :memory: database, another way to approach this problem is to look at using a memory filesystem for these databases.

These run approximately as fast, allow for databases to be re-used, and also allow for the database files to be copied (which Adapt takes advantage of when snapshots are on).

TIP

Linux has several memory-based filesystemsopen in new window that you could look at using. macOSopen in new window and Windowsopen in new window have options available as well.

If you're using Docker, you can add a memory tmpfs (memory) filesystem like this:

# docker-compose.yml
version: '3'
services:
  php:
    build:
      context: './build/php'
    tmpfs:
      - /var/www/html/database/adapt-test-storage
    environment:






 
 


NOTE

By default this directory will be owned by root inside your container. Most likely you will need to address ownership before Adapt can write to it.

MySQL & PostgreSQL Memory Databases

When using MySQL or PostgreSQL, it's possible to run your test-database server with a memory filesystem.

The database server will run like normal, but it will run faster because the location where it stores its data is in memory. Databases will persist until the server is restarted, so Adapt will be able to reuse them between test-runs.

TIP

Linux has several memory-based filesystemsopen in new window that you could look at using. macOSopen in new window and Windowsopen in new window have options available as well.

If you're using Docker, add a new MySQL container with a tmpfs (memory) filesystem for your tests:

# docker-compose.yml
version: '3'
services:
  mysql-tests:
    image: mysql:8.0
    tmpfs:
      - /var/lib/mysql
    environment:





 
 


And the same for PostgreSQL:

# docker-compose.yml
version: '3'
services:
  postgresql-tests:
    image: postgres:14
    tmpfs:
      - /var/lib/postgresql/data
    environment:





 
 


Migrations

Squash Migrations

Laravel 8 introduced the ability to squash migrationsopen in new window into a sql-dump file. When migrating from scratch, Laravel imports this first before running any newer migrations.

This would be a good idea if you have lots of migrations that update existing tables.

Seeders

Bulk Insert Seeder Data

Rows that are inserted individually will run slower than those combined into a single query. Combining the inserts will reduce the number of instructions sent to the database, which saves time.

# slower
INSERT INTO users ('name', 'email') VALUES ('bob', 'bob@example.com');
INSERT INTO users ('name', 'email') VALUES ('jane', 'jane@example.com');# faster
INSERT INTO users ('name', 'email') VALUES 
    ('bob', 'bob@example.com'), ('jane', 'jane@example.com'),;





 
 

Laravel factories can be used in this way too:

// instead of inserting rows in separate queries
User::factory()->count(1000)->create();

// insert all the rows in a single query
User::insert(
    User::factory()->count(1000)->make()->toArray()
);




 
 
 

Wrap Seeders in a Transaction

Wrapping insert queries in a transaction may give you a speed increase.

<?php
// database/seeders/DatabaseSeeder.phpclass DatabaseSeeder extends Seeder
{
    public function run()
    {
        DB::beginTransaction();

        $this->call(UsersSeeder::class);DB::commit();
    }
}







 




 


Disable Foreign Key Constraints

In MySQL, when tables use foreign keys, inserting will be quicker when FOREIGN_KEY_CHECKS is turned off. This will disable the extra checks MySQL needs to make. Just remember to turn it on afterwards.

<?php
// database/seeders/DatabaseSeeder.phpclass DatabaseSeeder extends Seeder
{
    public function run()
    {
        DB::statement("SET FOREIGN_KEY_CHECKS=0");

        $this->call(UsersSeeder::class);DB::statement("SET FOREIGN_KEY_CHECKS=1");
    }
}







 




 


Reduce Password Hash Rounds

Password hashing is designed to be slow. Reducing the number of iterationsopen in new window used will speed this up.

Recent versions of Laravel already apply this for you in phpunit.xml

// phpunit.xml
…
<env name="BCRYPT_ROUNDS" value="4"/>

TIP

If your seeders hash a lot of passwords, you could consider simply hard-coding them instead.

Use Mocks

Depending on what you're trying to test, it can be useful to mockopen in new window certain functionality in your tests.

Run Specific Tests

When working on a particular piece of code, adding --filter=xxx option as you run your testsopen in new window will help you by only running the relevant tests. Class names and test-methods can be searched for this way.

Re-run Failed Tests

Running only failed tests might help you focus on the problem you're currently working on. PHPUnit 7.3 introduced the ability to do thisopen in new window.

Use PHPUnit Directly

Running your tests via PHPUnitopen in new window directly ./vendor/bin/phpunit is slightly faster than using Laravel's php artisan test command. However, the difference is small.

Disable Xdebug, Use PCov

Xdebugopen in new window is a useful development tool. It performs a range of tasks, one of them is to monitor code-coverage. This is needed if you want your test suite to generate code-coverage reports.

However, it adds overhead as your code runs, and will slow down your tests. Disabling Xdebug will give your tests a speed boost.

If you'd still like to generate code-coverage reports, try PCovopen in new window instead as it's much faster. It focuses on code-coverage, without providing the other functionality that Xdebug does.

Support for PCov was added in PHPUnit 8 - if you're using an earlier version you could look at using \pcov\Clobberopen in new window.

TIP

If you use Dockerised containers in your project, you might like to consider having a separate container for testing. This way you can still use Xdebug in your development environment, but your tests can run without it.

Use SSD Storage

SSD storage is a lot faster than older HDD drives (with spinning disk platters). Upgrading to SSD drives may give you a speed boost, particularly if the code you're testing accesses the filesystem a lot.

See the troubleshooting section for more steps you can take when using a HDD.