jeffersongoncalves/pest-plugin-mysql-compat
| Install | |
|---|---|
composer require jeffersongoncalves/pest-plugin-mysql-compat |
|
| PHP: | ^8.2 |
| License: | MIT |
| Last Updated: | Sep 8, 2026 |
| Links: | GitHub · Packagist |

Pest Plugin MySQL Compat
Run a MySQL-flavored Laravel test suite against SQLite in memory without changing a single line of production SQL. The production query is the truth; the test database is what adapts.
A Laravel project with a legacy MySQL database usually runs its test suite on SQLite for
speed and isolation. Every query that uses a MySQL-only function then dies in the test, and
the usual fixes are all bad: rewriting the query to a SQLite-compatible subset (now the test
covers SQL that isn't the production SQL), marking the test ->todo() (the business rule
goes uncovered), or requiring a real MySQL for the suite (losing isolation and speed). This
package installs the missing/divergent MySQL functions as SQLite user-defined functions
(UDFs), with fidelity to NULL propagation, byte-length semantics, and legacy zero-dates —
so the query itself never has to change.
Installation
You can install the package via composer:
composer require jeffersongoncalves/pest-plugin-mysql-compat --dev
Usage
Pest
// tests/Pest.php
use JeffersonGoncalves\PestPluginMysqlCompat\Pest\Plugin;
Plugin::boot();
This is shorthand for:
// tests/Pest.php
use JeffersonGoncalves\PestPluginMysqlCompat\MysqlCompat;
pest()->beforeEach(fn () => MysqlCompat::install());
Alternative, without the Pest-specific helper — bind your own base TestCase instead:
// tests/Pest.php
uses(Tests\TestCase::class)->in('Feature', 'Unit');
// tests/TestCase.php
use JeffersonGoncalves\PestPluginMysqlCompat\MysqlCompat;
abstract class TestCase extends BaseTestCase
{
protected function setUp(): void
{
MysqlCompat::install();
parent::setUp();
}
}
PHPUnit / plain Laravel
abstract class TestCase extends BaseTestCase
{
protected function setUp(): void
{
MysqlCompat::install();
parent::setUp();
}
}
MysqlCompat::install() must run before the app resolves its first database connection —
it registers a connection resolver, and resolvers only affect connections created afterwards.
That is why it is called before parent::setUp(), not after.
Granular configuration
MysqlCompat::install(
functions: ['DATE_FORMAT', 'IF', 'CONCAT'], // default: all
rewriters: MysqlCompat::REWRITE_ALL, // reserved for Phase 2, no-op today
strict: true, // reserved for Phase 3, no-op today
);
Function catalog
Missing from SQLite — installed as UDFs
| Function | Signature | Fidelity note |
|---|---|---|
IF |
3 args | SQLite has native IIF; registering IF works |
JSON_UNQUOTE |
1 arg | see below — not a blind identity |
CONCAT |
variadic | propagates NULL |
SUBSTRING |
2–3 args | 1-indexed, like MySQL |
DATE_FORMAT |
2 args | treats '0000-00-00' as NULL |
LPAD |
3 args | |
LEFT |
2 args | |
RIGHT |
2 args | |
SUBSTRING_INDEX |
3 args | negative count counts from the end |
PERIOD_ADD |
2 args | returns the same type it received |
LAST_DAY |
1 arg | |
NOW |
0 args |
Native in SQLite, but with divergent semantics — overridden
| Function | SQLite native | MySQL | Action |
|---|---|---|---|
LENGTH |
LENGTH('ção') → 3 (characters) |
→ 5 (bytes) | overridden with strlen |
ROUND |
ROUND(1234.5, -2) → 1235.0 |
→ 1200 | overridden for negative precision |
LENGTH is the most treacherous divergence: on a column of digits (CPF, CNPJ, CEP) both
agree and the test passes; on accented text they silently diverge.
JSON_EXTRACT was measured to diverge too (JSON_EXTRACT returns a bare scalar in SQLite,
a quoted JSON value in MySQL) but is not overridden — JSON_UNQUOTE is adapted instead
to strip the surrounding quotes only when they are present, so it works over either shape.
Known limitations
WHEREofUPDATE/DELETEwith a float parameter is not rewritten — the (Phase 2) float-binding rewriter only targetsselect()/cursor(). No real case found yet; documented instead of covered speculatively.- Rewriting will be textual (Phase 2). SQL assembled at runtime by string concatenation with a fragment coming from data is risky territory; the lexer will refuse when unsure, and refusing means the original error comes back, not corrupted SQL.
GROUP_CONCATexists in both engines with the same default separator (,), but ordering andDISTINCTdiverge. It belongs in a future catalog addition with a parity test, not as "already works".- Text collation/ordering is not covered.
ORDER BYon accented text differs between the two engines — that is a collation problem, not a function problem. - The
LENGTHoverride changes behavior for anyone who was already writing SQL expecting SQLite's native character-countLENGTH. This is a breaking change for that consumer.
Roadmap
- Phase 1 — what pays for the package.
FunctionRegistry+ the 12 missing functions +LENGTHandROUNDoverrides. Pest and PHPUnit integration. Unit tests. - Phase 2 — the differentiator.
Lexer+FloatBindingRewriter(highest measured payoff) +IsNullRewriter+IntervalRewriter. - Phase 3 — confidence.
scandiagnostic command,strictmode, a MySQL-parity suite that runs against a real MySQL service in CI. - Phase 4 — reach. The demand-driven catalog (
IFNULL,CONCAT_WS,RPAD,DATEDIFF,DAY/MONTH/YEAR,ADDDATE,PERIOD_DIFF,STR_TO_DATE,TIMESTAMPDIFF,GROUP_CONCAT,FIND_IN_SET,FIELD,GREATEST,LEAST, …) and an adapter for consumers not on Laravel (Doctrine, plain PDO).
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security related issues, please email the author instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.
Related Packages
The most comprehensive WordPress testing framework built on Pest PHP v4. Include...
Create database dumps ready to be used with Codeception
Aggregate your incremental Laravel migration files into single migration for eac...
Laravel Facade and ServiceProvider for The Lightest PHP database framework to ac...
Aggregate your incremental Laravel migration files into single migration for eac...