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。
解决方案
核心问题
- Mock DB Facade破坏静态方法:用
Mockery::mock('alias:Illuminate\Support\Facades\DB')会替换整个Facade类,导致swap这类静态方法失效。 - 内存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:直接Mock
Illuminate\Database\Connection类,再用DB::swap()替换底层,不会影响Facade的静态方法。 - 处理链式调用:如果业务代码里用到
DB::connection()->getPdo(),必须在Mock中设置connection()返回自身,保证链式调用正常。
内容的提问来源于stack exchange,提问作者Jaquarh
相关产品推荐
相关产品推荐

