Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Database Mock Laravel Package

mpyw/laravel-database-mock

Experimental Laravel package that mocks underlying PDO connections with Mockery, letting you stub SELECT, INSERT, and UPDATE queries and results for fast, database-free tests. Requires PHP 8.2+ and Laravel 11+.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it as a dev dependency and pin the alpha explicitly:

composer require mpyw/laravel-database-mock:VERSION@alpha --dev

Confirm the baseline before writing tests: PHP ^8.2, Laravel ^11–^14, Mockery already in require-dev (^1.6.12), and acceptance of the transitive alpha mpyw/mockery-pdo. There is no service provider, no config file, no publish step — if you are registering anything, you have misread the setup.

Where to look first, in order:

  1. The README SELECT example — it is the entire mental model in ten lines: declare SQL, declare rows, call the model.
  2. mpyw/mockery-pdo — this is where the expressive API actually lives (shouldSelect, shouldFetchAllReturns, shouldInsert, shouldUpdateOne, shouldUpdateMany, shouldDelete). laravel-database-mock only wires that PDO double into Laravel's Connection.
  3. DBMock::mockEachPdo() with reader() / writer() — the read/write replica variant, and the one capability that justifies the alpha dependency.

First use case — spike one query shape, not the whole suite:

$pdo = DBMock::mockPdo();
$pdo->shouldSelect('select * from `users`')
    ->shouldFetchAllReturns([[
        'id' => 1, 'name' => 'John', 'email' => '[email protected]',
        'created_at' => '2020-01-01 00:00:00', 'updated_at' => '2020-01-01 00:00:00',
    ]]);

$this->assertSame('John', User::first()->name);

Run this in a single throwaway test class against a repository or query object. Do not wire it into a base test case or setUp() until the spike has proven itself — a 0.0.1-alpha package should earn its place in your harness.

Implementation Patterns

Mock per test, per connection, at the call site. Put DBMock::mockPdo() at the top of the test body. The double replaces the PDO beneath the connection for that test only, keeping the substitution visible and preventing leakage into tests that should remain honest.

Script the conversation — do not stub a single call. The mock is expectation-based and ordered. Every query the code under test will issue must be declared, in sequence:

Carbon::setTestNow('2020-01-02 00:00:00');

$pdo = DBMock::mockPdo();
$pdo->shouldSelect('select * from `users` where `email` = ? limit 1', ['[email protected]'])
    ->shouldFetchAllReturns([[
        'id' => 2, 'name' => 'John', 'email' => '[email protected]',
        'created_at' => '2020-01-01 00:00:00', 'updated_at' => '2020-01-01 00:00:00',
    ]]);
$pdo->shouldUpdateOne(
    'update `users` set `email` = ?, `users`.`updated_at` = ? where `id` = ?',
    ['[email protected]', '2020-01-02 00:00:00', 2]
);

$user = User::query()->where('email', '[email protected]')->first();
$user->forceFill(['email' => '[email protected]'])->save();

Inserts need two expectations. shouldInsert() asserts the statement; the returned ID is a separate Mockery expectation because Laravel calls lastInsertId() afterwards:

$pdo->shouldInsert(
    'insert into `users` (`name`, `email`, `updated_at`, `created_at`) values (?, ?, ?, ?)',
    ['John', '[email protected]', '2020-01-01 00:00:00', '2020-01-01 00:00:00']
);
$pdo->expects('lastInsertId')->andReturn(2);

Freeze time on every write path. Carbon::setTestNow() is effectively mandatory — timestamps are baked into both the SQL bindings and the hydrated attributes. A drifting clock produces failures that read like package bugs but are pure clock drift.

Assert hydration, not just SQL. Because the seam sits below the ORM, Eloquent still hydrates, casts, appends accessors, and wraps collections. Asserting on ->toArray() covers model behavior alongside the query assertion — that fidelity is the main advantage over Model::shouldReceive().

Replica routing — the differentiator. When the connection is configured with separate read/write hosts, use mockEachPdo() and assert that writes land on writer() and reads on reader():

$pdos = DBMock::mockEachPdo();
$pdos->reader()
    ->shouldSelect('select * from `users` where `email` = ? limit 1', ['[email protected]'])
    ->shouldFetchAllReturns([[...]]);
$pdos->writer()
    ->shouldUpdateOne('update `users` set `email` = ?, `users`.`updated_at` = ? where `id` = ?', [...]);

This is a bug class a single in-memory SQLite database structurally cannot catch.

Failure-path tests. Leave an expectation unmet, or use andThrow() on the PDO double, to exercise retry/rollback branches without a database. Handle DB::transaction() deliberately — see gotchas.

Where this belongs in your suite. Carve out a small "SQL contract" tier: repositories, reporting/billing query builders, replica routing, connection-failure paths. Keep RefreshDatabase/SQLite for everything else. A tests/SqlContract/ directory with the convention documented beside it is the durable shape.

Gotchas and Tips

  • Tests bind to generated SQL text, not behavior. A grammar tweak or query-builder refactor silently breaks assertions. Treat each mocked string as a snapshot you own. Keep the tier small and name tests after query intent, not the model.
  • Backticks mean MySQL grammar. The README examples assume MySQL quoting. On sqlite or pgsql the identifiers quote differently ("users") and every assertion fails. Match your test connection's configured driver to the strings you write.
  • Bindings are positional and exact. Timestamps, decimals, booleans, and enum casts must match the driver's parameter binding (true vs 1). When a binding assertion fails, dump both arrays — the diff is usually a datetime format or a boolean cast.
  • lastInsertId() is a separate expectation. Forget it and you get a null ID plus a confusing model assertion failure far from the real cause.
  • The mock is strict about unexpected queries. A lazy-loaded relation, an accessor touching another table, or a stray exists() will fail the test. Eager-load deliberately and script any extra query.
  • The mock is stateless. It does not remember what you inserted. A multi-step flow needs a shouldSelect returning the row you "wrote" — you are scripting a conversation, not simulating a database.
  • Database behavior is invisible. No triggers, FK cascades, unique constraints, generated columns, collation, or real isolation. Never let this tier replace integration coverage; it verifies calls, not behavior.
  • Transactions won't behave realistically. DB::transaction() issues beginTransaction/commit/rollBack on the handle. Script or relax those expectations explicitly; rollback-on-exception tests need extra care.
  • Mockery teardown still applies. Add Mockery::close() to tearDown() if you don't already have it — unmet expectations surface there, and the message won't mention this package.
  • Pin the alpha exactly and commit the lockfile. Both this package and mpyw/mockery-pdo can move. Run these tests in CI against the oldest and newest supported Laravel versions.
  • Pest friction. The API is Mockery-shaped, so it reads awkwardly beside fake()/expect(). Fine in a dedicated directory; don't mix styles in one file.
  • Not for non-PDO stores. Redis, MongoDB, or query-builder-only paths have nothing here to intercept.
  • Extension points are thin by design. There is no config to tweak; customization means wrapping DBMock in your own base helper — which also gives you one place to change when the alpha API shifts.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings