Laravel中无法部分模拟Log门面的问题排查与求助
如何正确测试Laravel中类的日志操作?
问题场景
我尝试模拟Laravel的Log门面来测试MyClass的myFunction方法是否执行日志操作,测试代码如下:
public function test_process_verify_card() { Log::shouldReceive('error')->once(); Log::makePartial(); $class = new MyClass(); $class->myFunction(); }
MyClass的代码(注:代码存在拼写错误:Logg应为Log,loggger应为logger):
class MyClass { public function __construct() { $this->logger = Log::channel('test'); } public function myFunction() { // ... 一些逻辑 $this->logger->error('Errror!!'); } }
第一次运行测试抛出错误:
Call to a member function runningUnitTests() on null
at vendor/laravel/framework/src/Illuminate/Log/LogManager.php:568
调试发现LogManager的$this->app不为null,且此前模拟Date门面正常。
更新后尝试使用partialMock方法,测试代码如下:
public function test_process_verify_card() { $this->partialMock(Logger::class, function (MockInterface $mock) { $mock->shouldReceive('error')->once(); }); $class = new MyClass(); $class->myFunction(); }
仍无法正常运行,报错:
Method error(
) from Mockery_0_Illuminate_Log_Logger should be called exactly 1 times but called 0 times. at vendor/phpunit/phpunit/phpunit:98
错误原因分析
- 第一次测试错误:直接Mock Log门面的
error方法,但MyClass中是通过Log::channel('test')获取特定通道的Logger实例,再调用该实例的error方法,并非直接调用Log门面的error方法,因此Mock对象不匹配。 - 第二次测试错误:
partialMock全局的Logger类,但Log::channel('test')会返回一个独立的Logger实例,和Mock的全局类没有关联,导致Mock未被命中。
正确的测试方法
方式1:Mock Log门面的channel方法,返回指定Mock Logger
通过Mock Log门面的channel方法,让它返回我们预先创建的Mock Logger实例,这样MyClass获取到的就是Mock对象:
use Mockery; use Illuminate\Support\Facades\Log; use Illuminate\Log\Logger; public function test_process_verify_card() { // 创建Mock的Logger实例,指定预期调用 $mockLogger = Mockery::mock(Logger::class); $mockLogger->shouldReceive('error')->once()->with('Errror!!'); // 让Log门面的channel方法返回这个Mock实例 Log::shouldReceive('channel') ->with('test') ->andReturn($mockLogger); $class = new MyClass(); $class->myFunction(); }
方式2:依赖注入Logger(更符合Laravel设计原则)
修改MyClass,通过构造函数注入指定通道的Logger,避免直接在类内部调用Log门面,让测试更简洁:
use Illuminate\Log\Logger; class MyClass { protected $logger; // 构造函数注入test通道的Logger public function __construct(Logger $logger) { $this->logger = $logger; } public function myFunction() { // ... 一些逻辑 $this->logger->error('Errror!!'); } }
对应的测试代码可以直接注入Mock实例:
use Mockery\MockInterface; use Illuminate\Log\Logger; public function test_process_verify_card() { $this->mock(Logger::class, function (MockInterface $mock) { $mock->shouldReceive('error')->once()->with('Errror!!'); }); // 通过容器解析MyClass,自动注入Mock的Logger $class = app(MyClass::class); $class->myFunction(); }
或者手动传入Mock实例:
use Mockery; use Illuminate\Log\Logger; public function test_process_verify_card() { $mockLogger = Mockery::mock(Logger::class); $mockLogger->shouldReceive('error')->once()->with('Errror!!'); $class = new MyClass($mockLogger); $class->myFunction(); }
补充说明
- 先修正
MyClass中的拼写错误(Logg改为Log,loggger改为logger),否则实际运行代码也会报错。 - 依赖注入的方式更符合Laravel的依赖注入原则,降低类与门面的耦合度,同时简化测试逻辑。
内容的提问来源于stack exchange,提问作者simpsons3
相关产品推荐
相关产品推荐

