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

Laravel:如何用Mockery正确模拟服务绑定解决未生效问题

问题描述

现有如下业务类:

class Testclass
{
    public WebshopappApiClient $client;

    public function __construct(Webshop $webshop)
    {
        $this->client = app()->make(WebshopappApiClient::class, ['webshop' => $webshop]);
    }

    public function callTestMethod()
    {
        // 期望返回Mock的空stdClass对象
        dd($this->client->shipments);
    }
}

对应的服务提供者:

<?php

namespace App\Providers;

use App\Webshop;
use Illuminate\Support\ServiceProvider;

class WebshopappApiClientServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(\WebshopappApiClient::class, function ($app, $params): \WebshopappApiClient {
            /** @var Webshop $webshop */
            $webshop = $params['webshop'];

            $key = $webshop->key;
            $value = $webshop->value;

            return new \WebshopappApiClient($key, $value);
        });
    }
}

尝试用Mockery模拟$this->client->shipments的调用,测试代码如下:

public function testShipWithTracking(): void
{
    $mockClient = Mockery::mock(\WebshopappApiClient::class);
    $mockClient->shipments = Mockery::mock('stdClass');
    app()->instance(\WebshopappApiClient::class, $mockClient);

    $webshop = new Webshop([
         'key' => 'test',
         'value' => 'test',
    ]);
    $testClass = new Testclass($webshop);
    // 实际仍返回真实对象而非Mock的空stdClass
    $testClass->callTestMethod();
}

问题:模拟未生效,如何在不修改Testclass构造函数的前提下,确保服务绑定被Mock对象覆盖?核心目标是模拟该API客户端类的所有API响应,且该类在遗留代码中被大量调用。

解决方案

问题根源

app()->instance()注册的共享实例,仅在不带参数调用app()->make()时生效。而Testclass中调用app()->make(WebshopappApiClient::class, ['webshop' => $webshop])时,Laravel会优先使用服务提供者中注册的带参数绑定回调,忽略instance注册的实例,导致Mock失效。

解决方法:重新绑定带参数的服务回调

直接替换服务容器中WebshopappApiClient::class的绑定逻辑,让它无论传入什么参数都返回Mock实例:

public function testShipWithTracking(): void
{
    // 创建Mock的shipments对象
    $mockShipments = Mockery::mock(stdClass::class);
    // 创建Mock的API客户端,并赋值shipments属性
    $mockClient = Mockery::mock(\WebshopappApiClient::class);
    $mockClient->shipments = $mockShipments;

    // 重新绑定服务,覆盖原有的带参数回调
    $this->app->bind(\WebshopappApiClient::class, function ($app, $params) use ($mockClient) {
        return $mockClient;
    });

    $webshop = new Webshop([
         'key' => 'test',
         'value' => 'test',
    ]);
    $testClass = new Testclass($webshop);
    $testClass->callTestMethod(); // 现在会返回Mock的shipments对象
}

另一种简化写法(利用Laravel测试辅助函数)

如果使用Laravel的测试框架,可以直接用$this->mock()方法,同时处理构造参数的兼容:

public function testShipWithTracking(): void
{
    $mockShipments = Mockery::mock(stdClass::class);

    // Mock客户端类,指定解析时忽略构造参数,返回Mock实例
    $this->mock(\WebshopappApiClient::class, function ($mock) use ($mockShipments) {
        $mock->shipments = $mockShipments;
        // 允许任意构造参数调用
        $mock->shouldReceive('__construct')->withAnyArgs()->andReturnSelf();
    });

    $webshop = new Webshop([
         'key' => 'test',
         'value' => 'test',
    ]);
    $testClass = new Testclass($webshop);
    $testClass->callTestMethod();
}

注意事项

  • 测试结束后需清理Mockery,避免影响其他测试:可以在测试类的tearDown方法中添加Mockery::close();,或使用Laravel内置的Mockery集成(Laravel 8+默认支持)。
  • 如果需要模拟特定方法的返回值,可在Mock对象上添加shouldReceive断言,例如:$mockShipments->shouldReceive('create')->withAnyArgs()->andReturn(true);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 10:14:51