You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel单元测试中如何Mock DB门面的getPdo方法?

问题背景

接手的项目因过度预加载引发部分区域无限循环,为优化性能,部分SQL改用getPdo方法编写。现需为这些修改编写单元测试:Mock getPdo 指向内存SQLite,创建表my_table后断言查询结果为空。


尝试过程与问题

初始测试代码与报错

(new \PDO('sqlite::memory:'))->exec('CREATE TABLE my_table (id INTEGER PRIMARY KEY, name TEXT)');

$mock = Mockery::mock('alias:Illuminate\Support\Facades\DB');
$mock->shouldReceive('connection')
    ->andReturnSelf();

$mock->connection()->shouldReceive('getPdo')
    ->andReturnUsing(function () {
        return new \PDO('sqlite::memory:');
    });

DB::swap($mock);

$stmt = DB::connection()->getPdo()->prepare('select * from my_table');
$stmt->execute();
$this->assertEquals([], $stmt->fetchAll(\PDO::FETCH_OBJ));

运行时错误:

Static method Illuminate\Support\Facades\DB::swap() does not exist on this mock object

使用Laravel 8.8X.X版本,正常情况下DB::swap可切换底层接口,求解决办法。


更新1:改用内置Mock,仍报错

测试代码:

DB::shouldReceive('connection->getPdo')
    ->andReturnUsing(function () {
        return new \PDO('sqlite::memory:');
});

错误信息:

SQLSTATE[HY000]: General error: 1 no such table: my_table


更新2:Mock Connection解决swap问题,但表仍不存在

测试代码:

(new \PDO('sqlite::memory:'))->exec('CREATE TABLE my_table (id INTEGER PRIMARY KEY, name TEXT)');

$connection = Mockery::mock('Illuminate\Database\Connection');
$connection->shouldReceive('connection')->andReturnSelf();
$connection->connection()->shouldReceive('getPdo')
    ->andReturnUsing(fn() => new \PDO('sqlite::memory:'));

DB::swap($connection);

$stmt = DB::connection()->getPdo()->prepare('select * from my_table');
$stmt->execute();
$this->assertEquals([], $stmt->fetchAll(\PDO::FETCH_OBJ));

错误信息:

SQLSTATE[HY000]: General error: 1 no such table: my_table

验证Mock是否成功的测试能通过:

$connection->connection()->shouldReceive('getPdo')->andReturnUsing(fn() => []);
$this->assertEquals([], DB::connection()->getPdo());

调试发现创建表时exec返回0。


解决方案

核心问题

  1. Mock DB Facade破坏静态方法:用Mockery::mock('alias:Illuminate\Support\Facades\DB')会替换整个Facade类,导致swap这类静态方法失效。
  2. 内存SQLite连接不共享:每次new \PDO('sqlite::memory:')都会生成全新的内存数据库实例,创建表的连接和查询用的连接不是同一个,自然找不到表。

正确测试代码

// 创建唯一的内存SQLite连接实例,后续所有操作复用它
$pdo = new \PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE my_table (id INTEGER PRIMARY KEY, name TEXT)');

// Mock Connection对象,指定getPdo返回刚才创建的实例
$connection = Mockery::mock(\Illuminate\Database\Connection::class);
$connection->shouldReceive('getPdo')->andReturn($pdo);
// 若代码中用到DB::connection()链式调用,需让connection()返回自身
$connection->shouldReceive('connection')->andReturnSelf();

// 替换DB的底层连接
DB::swap($connection);

// 执行查询并断言结果为空
$stmt = DB::connection()->getPdo()->prepare('select * from my_table');
$stmt->execute();
$this->assertEquals([], $stmt->fetchAll(\PDO::FETCH_OBJ));

关键说明

  • 复用PDO实例:内存SQLite的生命周期和PDO实例绑定,必须保证建表、查询用同一个实例,不能每次新建。
  • Mock Connection而非Facade:直接MockIlluminate\Database\Connection类,再用DB::swap()替换底层,不会影响Facade的静态方法。
  • 处理链式调用:如果业务代码里用到DB::connection()->getPdo(),必须在Mock中设置connection()返回自身,保证链式调用正常。

内容的提问来源于stack exchange,提问作者Jaquarh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 14:45:33