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);
关键细节说明
- 链式调用Mock:
Currency::whereIn(...)返回的是Builder实例,必须先MockBuilder的get()方法,再让whereIn()返回这个Mock对象,才能完整拦截查询链。 - 静态方法拦截:直接在模型Mock上声明
shouldReceive('all')和shouldReceive('whereIn'),才能覆盖静态调用逻辑。 - 数据结构对齐:提前用
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
相关产品推荐
相关产品推荐

