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

Laravel 8单元测试:如何Mock模型伪造数据替代读取数据库?

问题解决:Mock Eloquent模型静态方法(all/whereIn)拦截数据库查询

你的测试代码没生效的核心原因是:你Mock的是Currency类的实例,但Currency::all()、Currency::whereIn()是静态调用,实际走的是Eloquent模型的静态方法/查询构造器链,不是实例方法,所以替换类实例不会拦截这些静态调用,导致仍然读取数据库数据。

正确的Mock方式

需要针对模型的静态调用和查询构造器链式调用进行Mock,以下是修正后的测试代码:

// 1. 构造测试用的汇率数据,提前做好keyBy处理
$currencyData = collect([
    new Currency(['iso_code' => 'USD', 'current_rate' => 1.0, 'previous_rate' => 1.0]),
    new Currency(['iso_code' => 'EUR', 'current_rate' => 0.8, 'previous_rate' => 0.9]),
    new Currency(['iso_code' => 'GBP', 'current_rate' => 1.2, 'previous_rate' => 1.1]),
    new Currency(['iso_code' => 'CAD', 'current_rate' => 1.3, 'previous_rate' => 1.2]),
])->keyBy('iso_code');

// 2. Mock查询构造器:因为whereIn返回的是Builder实例,需要模拟get()方法
$queryMock = Mockery::mock(\Illuminate\Database\Eloquent\Builder::class);
$queryMock->shouldReceive('get')->andReturn($currencyData);

// 3. Mock Currency模型的静态方法
$currencyMock = Mockery::mock(Currency::class);
// 拦截all()静态调用,返回预设数据
$currencyMock->shouldReceive('all')->andReturn($currencyData);
// 拦截whereIn静态调用,返回Mock的查询构造器
$currencyMock->shouldReceive('whereIn')
    ->with('iso_code', Mockery::any()) // 可替换为具体参数['EUR','GBP','USD']以提高测试严谨性
    ->andReturn($queryMock);

// 4. 将Mock的模型绑定到容器
$this->app->instance(Currency::class, $currencyMock);

// 5. 执行测试逻辑
$currencyService = new CurrencyConversionService();
$results = $currencyService->currencyRates([
    'base_currency' => 'USD',
    'iso_codes' => ['EUR', 'GBP']
]);
dd($results);

关键细节说明

  1. 链式调用Mock:Currency::whereIn(...)返回的是Builder实例,必须先MockBuilder的get()方法,再让whereIn()返回这个Mock对象,才能完整拦截查询链。
  2. 静态方法拦截:直接在模型Mock上声明shouldReceive('all')和shouldReceive('whereIn'),才能覆盖静态调用逻辑。
  3. 数据结构对齐:提前用keyBy('iso_code')处理测试数据,和业务代码中的数据结构保持一致,避免后续逻辑出错。

可选简化方案(Laravel内置Mock)

如果你的测试类继承了Laravel的TestCase,可以用框架内置的mock方法简化代码:

$this->mock(Currency::class, function ($mock) use ($currencyData) {
    $queryMock = Mockery::mock(\Illuminate\Database\Eloquent\Builder::class);
    $queryMock->shouldReceive('get')->andReturn($currencyData);
    
    $mock->shouldReceive('all')->andReturn($currencyData);
    $mock->shouldReceive('whereIn')->with('iso_code', ['EUR','GBP','USD'])->andReturn($queryMock);
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 08:47:13