Driver matrix
DriverMatrix runs a suite across database drivers, so a SQLite-only run doesn’t miss what the engines disagree on — a LIKE without ESCAPE, for example, is green on Postgres and returns zero rows on SQLite.
use RoundlyConsulting\Testing\Database\DriverMatrix;
// Only in a TestCase that does NOT extend PackageTestCase (which calls it for you):
protected function defineEnvironment($app): void
{
DriverMatrix::configure($app);
}
// Skip a driver-specific case visibly on the wrong leg:
it('uses jsonb')->skip(fn () => DriverMatrix::driver() !== 'pgsql');PackageTestCase calls DriverMatrix::configure() for you. configure() points the default testing connection at TESTING_DB_DRIVER (default: in-memory SQLite with foreign keys on) and registers pgsql and mysql as named connections — present but unreachable off a driver leg, so a gated assertion skips visibly rather than never firing.
TESTING_DB_DRIVER must be one of sqlite, pgsql, mysql or mariadb. Anything else — postgres, sqlsrv — throws InvalidArgumentException instead of quietly running the leg on SQLite and skipping every engine-gated test green.
Environment
| Variable | Default | Purpose |
|---|---|---|
TESTING_DB_DRIVER | sqlite | The leg’s driver: sqlite, pgsql, mysql or mariadb — anything else throws InvalidArgumentException. The only variable that moves the suite off SQLite. |
TESTING_DB_HOST | 127.0.0.1 | Engine host. |
TESTING_DB_PORT | 5432 / 3306 | Engine port (Postgres / MySQL default). |
TESTING_DB_DATABASE | testing | Database name. |
TESTING_DB_USERNAME | testing / root | User (Postgres / MySQL default). |
TESTING_DB_PASSWORD | '' | Password. |
A CI leg must export TESTING_DB_DRIVER. The location variables only say where the engine is; exporting them without the driver leaves the whole suite on SQLite — a “pgsql” job that never touches Postgres. The location applies only to the leg’s own driver, so off-leg connections fall back to their defaults and fail fast instead of hanging on the wrong engine.
Running locally against Postgres
TESTING_DB_DRIVER=pgsql \
TESTING_DB_HOST=127.0.0.1 TESTING_DB_PORT=5432 \
TESTING_DB_DATABASE=testing TESTING_DB_USERNAME=testing TESTING_DB_PASSWORD=secret \
vendor/bin/pestA Postgres CI leg
The package’s own GitHub Actions job, ready to lift:
test-pgsql:
runs-on: ubuntu-latest
name: P8.4 - pgsql real-engine
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: testing
POSTGRES_USER: testing
POSTGRES_PASSWORD: secret
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v7
- uses: shivammathur/setup-php@v2
with:
php-version: 8.4
extensions: pdo, sqlite, pdo_sqlite, pgsql, pdo_pgsql
- run: composer update --prefer-stable --prefer-dist --no-interaction
- name: Execute tests against postgres
env:
# TESTING_DB_DRIVER is what moves the suite off sqlite.
TESTING_DB_DRIVER: pgsql
TESTING_DB_HOST: 127.0.0.1
TESTING_DB_PORT: 5432
TESTING_DB_DATABASE: testing
TESTING_DB_USERNAME: testing
TESTING_DB_PASSWORD: secret
run: vendor/bin/pest --ciIsolated probe connections
The pgsql and mysql connections that configure() registers are isolated probes: on Postgres their search_path is a dedicated testing_probe schema, on MySQL a dedicated testing_probe database, both created on demand. The real-engine runner drops its target clean, and this isolation makes it structurally impossible for a probe run to drop the live suite’s tables.
API
| Method | Purpose |
|---|---|
DriverMatrix::configure($app) | Point the testing connection at the matrix driver and register the pgsql/mysql probe connections. |
DriverMatrix::driver() | The active driver — TESTING_DB_DRIVER, defaulting to sqlite; an unknown value throws. Use it in ->skip() guards. |
DriverMatrix::connectionConfig(?string $driver = null) | The resolved connection config for the active (or given) driver — to register it under another name or assert on it. |
DriverMatrix::probeConnectionConfig(string $driver) | The same config confined to the testing_probe namespace. |
DriverMatrix::prepareProbe(string $connection) | Create the probe namespace if it’s missing; a no-op for any connection that isn’t an isolated probe. |
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.