-
Notifications
You must be signed in to change notification settings - Fork 0
Testing Your Application
DBAL is intentionally easy to test against. The package's own suite is 70 tests / 113 assertions running in under 100ms against SQLite in-memory. You can borrow the same patterns for your application tests.
use InitORM\DBAL\Connection\Connection;
use PHPUnit\Framework\TestCase;
abstract class DbTestCase extends TestCase
{
protected Connection $db;
protected function setUp(): void
{
$this->db = new Connection([
'driver' => 'sqlite',
'database' => ':memory:',
'charset' => '',
]);
$this->migrate();
$this->seed();
}
private function migrate(): void
{
$this->db->getPDO()->exec('CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
active INTEGER NOT NULL DEFAULT 1
)');
}
private function seed(): void
{
$this->db->query(
"INSERT INTO users (name, email) VALUES (:n, :e)",
['n' => 'Alice', 'e' => 'alice@example.com']
);
}
}Every test gets a clean, isolated database. No fixtures to clean up, no ordering concerns.
SQLite is fast and convenient, but it is not a drop-in equivalent for the database you ship against. Differences that bite:
| Behaviour | SQLite | MySQL | PostgreSQL |
|---|---|---|---|
numRows() on SELECT
|
unreliable | OK with buffered | OK with buffered |
| Boolean columns | INTEGER 0/1 | TINYINT 0/1 | TRUE/FALSE |
| Date / time types | TEXT | DATETIME etc. | TIMESTAMP etc. |
| Transactional DDL | yes | implicit commit | yes |
| String vs blob comparisons | dynamic type | strict | strict |
LIMIT … OFFSET … precedence |
OK | OK | requires both |
For anything that touches these — boolean filters, date arithmetic, multi-statement migrations — run your tests against the real database in CI as well. The library's own CI uses SQLite for unit tests; integration suites against real engines belong in your application.
If you wrap DBAL in a repository, type-hint the interface:
use InitORM\DBAL\Connection\Interfaces\ConnectionInterface;
final class UserRepository
{
public function __construct(private ConnectionInterface $db) {}
public function findById(int $id): ?array
{
return $this->db->query(
'SELECT id, name, email FROM users WHERE id = :id',
['id' => $id]
)->asAssoc()->row();
}
}…and inject a real, SQLite-backed Connection in the test:
final class UserRepositoryTest extends DbTestCase
{
public function test_finds_existing_user(): void
{
$repo = new UserRepository($this->db);
$user = $repo->findById(1);
self::assertSame('Alice', $user['name']);
}
public function test_returns_null_for_missing_user(): void
{
$repo = new UserRepository($this->db);
self::assertNull($repo->findById(9999));
}
}There is rarely a reason to mock Connection — the SQLite path is
already fast and exercises real PDO behaviour. Mocks tend to hide
binding bugs; the in-memory database catches them.
public function test_repository_runs_expected_queries(): void
{
$this->db->setQueryLogs(true);
(new UserRepository($this->db))->findById(1);
$queries = array_column($this->db->getQueryLogs(), 'query');
self::assertContains('SELECT id, name, email FROM users WHERE id = :id', $queries);
}If you need to verify that a path triggers a log message, plug a callable in as the sink:
$messages = [];
$db = new Connection([
'driver' => 'sqlite',
'database' => ':memory:',
'charset' => '',
'log' => static function (string $m) use (&$messages): void {
$messages[] = $m;
},
]);
try {
$db->query('SELECT * FROM nope');
} catch (\Throwable $e) {
// expected
}
self::assertNotEmpty($messages);
self::assertStringContainsString('SELECT * FROM nope', $messages[0]);For corner cases where you want every prepared statement to be intercepted, swap in a custom factory through the second constructor argument:
use InitORM\DBAL\DataMapper\Interfaces\DataMapperFactoryInterface;
use InitORM\DBAL\DataMapper\Interfaces\DataMapperInterface;
final class SpyFactory implements DataMapperFactoryInterface
{
public array $prepared = [];
public function createDataMapper(\PDOStatement $stmt): DataMapperInterface
{
$this->prepared[] = $stmt->queryString;
return new \InitORM\DBAL\DataMapper\DataMapper($stmt);
}
}
$spy = new SpyFactory();
$db = new Connection($credentials, $spy);
(new UserRepository($db))->findById(1);
self::assertContains(
'SELECT id, name, email FROM users WHERE id = :id',
$spy->prepared
);- Use
:memory:SQLite (~0.1ms per test setup) rather than a temp file. - Share the schema-creation step across tests with PHPUnit's
setUpBeforeClass— but recreateConnectionper test so state is isolated. - Disable query logging in setup-heavy tests; it has a small but real per-call cost.
- Architecture for why the testing model looks like this.
- Factories and DI for more advanced swap-ins.
InitORM DBAL · MIT · maintained by Muhammet ŞAFAK · part of the InitORM stack
Getting Started
Core
Cross-Cutting
Reference
Upgrading
Project