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

如何在PHPUnit中模拟未通过构造/方法传入的PHP类?

在PHPUnit中Mock内部实例化的类(类似JavaScript模块Mock)

问题场景

我在使用PHPUnit编写单元测试时,遇到一个问题:需要测试的类内部直接实例化了另一个类(未通过构造函数或方法参数传入),比如下面的SmsSender类中直接new Sms(),想知道PHPUnit中有没有类似JavaScript里Mock模块的实现方式,能够全局Mock这个SomeLibrary\Resource\Sms类,存根其方法并断言调用参数是否正确。

待测试业务代码

<?php
use SomeLibrary\Resource\Sms;

final class SmsSender
{
    public function __construct(private array $config) {}

    public function send(string $phone, string $content): void
    {
        $sms = new Sms();
        $sms->sendToOne($phone, $content, $this->config['sender']);
    }
}

初始测试代码

<?php declare(strict_types = 1);

namespace Tests\Unit\App\Services\SmsSender;

use App\Services\SmsSender;
use Tests\Helpers\TestCase;

class SmsSenderTest extends TestCase
{
    private array $config;

    private SmsSender $service;

    public function setUp(): void
    {
        $this->config = [
            'apiKey' => 'mock-api-key',
            'apiSecret' => 'mock-api-secret',
            'sender' => 'my-company-name'
        ];

        $this->service = new SmsSender($this->config);
    }

    public function testSend(): void
    {
        // how to mock the SomeLibrary\Resource\Sms ?
        $this->service->send('+3344334', 'dfddfdfdf');
    }
}

解决方案1:使用Mockery别名Mock(无需修改业务代码)

该方案借助Mockery的alias:前缀实现全局类Mock,让业务代码中new Sms()生成的是Mock实例,无需改动原有业务逻辑。

实现后的测试代码

<?php declare(strict_types = 1);

namespace Tests\Unit\App\Services\SmsSender;

use App\Services\SmsSender;
use Mockery;
use Mockery\MockInterface;
use SomeLibrary\Resource\Sms;
use Tests\Helpers\TestCase;

class SmsSenderTest extends TestCase
{
    private array $config;
    private SmsSender $service;
    private MockInterface $smsMock;

    public function setUp(): void
    {
        parent::setUp();

        $this->config = [
            'apiKey' => 'mock-api-key',
            'apiSecret' => 'mock-api-secret',
            'sender' => 'my-company-name'
        ];

        // 创建Sms类的别名Mock
        $this->smsMock = Mockery::mock('alias:' . Sms::class);
        $this->service = new SmsSender($this->config);
    }

    public function testSend(): void
    {
        $phone = '+3344334';
        $content = 'dfddfdfdf';
        $sender = $this->config['sender'];

        // 断言sendToOne方法会被正确参数调用一次
        $this->smsMock->shouldReceive('sendToOne')
            ->once()
            ->with($phone, $content, $sender);

        $this->service->send($phone, $content);
    }

    public function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }
}

贡献者:[标注第一个解决方案提供者名称]


解决方案2:重构为依赖注入(更符合SOLID原则)

该方案通过重构业务代码,将Sms类的实例化转为依赖注入,既保持原有代码兼容性,又让测试时可直接传入Mock实例,代码结构更灵活可测。

重构后的业务代码

<?php
use SomeLibrary\Resource\Sms;

final class SmsSender
{
    public function __construct(
        private array $config,
        private ?Sms $sms = null
    ) {
        // 未传入Sms实例时自动初始化,兼容原有调用逻辑
        $this->sms = $sms ?? new Sms();
    }

    public function send(string $phone, string $content): void
    {
        $this->sms->sendToOne($phone, $content, $this->config['sender']);
    }
}

实现后的测试代码

<?php declare(strict_types = 1);

namespace Tests\Unit\App\Services\SmsSender;

use App\Services\SmsSender;
use PHPUnit\Framework\MockObject\MockObject;
use SomeLibrary\Resource\Sms;
use Tests\Helpers\TestCase;

class SmsSenderTest extends TestCase
{
    private array $config;
    private SmsSender $service;
    private MockObject $smsMock;

    public function setUp(): void
    {
        parent::setUp();

        $this->config = [
            'apiKey' => 'mock-api-key',
            'apiSecret' => 'mock-api-secret',
            'sender' => 'my-company-name'
        ];

        // 使用PHPUnit原生MockBuilder创建Sms的Mock实例
        $this->smsMock = $this->getMockBuilder(Sms::class)
            ->disableOriginalConstructor()
            ->getMock();

        // 传入Mock实例初始化SmsSender
        $this->service = new SmsSender($this->config, $this->smsMock);
    }

    public function testSend(): void
    {
        $phone = '+3344334';
        $content = 'dfddfdfdf';
        $sender = $this->config['sender'];

        // 断言sendToOne方法的调用参数和次数
        $this->smsMock->expects($this->once())
            ->method('sendToOne')
            ->with($phone, $content, $sender);

        $this->service->send($phone, $content);
    }
}

贡献者:[标注第二个解决方案提供者名称]


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:07:58