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

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

错误原因分析

  1. 第一次测试错误:直接Mock Log门面的error方法,但MyClass中是通过Log::channel('test')获取特定通道的Logger实例,再调用该实例的error方法,并非直接调用Log门面的error方法,因此Mock对象不匹配。
  2. 第二次测试错误: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 06:31:21