部分模拟一个类而不影响 PHP 中的私有属性

Posted

技术标签:

【中文标题】部分模拟一个类而不影响 PHP 中的私有属性【英文标题】:partially mocking a class without affecting the private properties in PHP 【发布时间】:2020-10-03 13:58:57 【问题描述】:

我有一个包含很多方法的类,由于 mysql 和内存中的 sqlite 数据库之间的某些 sql 不兼容,我只需要模拟一个方法。

class OrderService implements OrderServiceContract

    protected $deliveryService;

    public function __construct(Delivery $deliveryService) // DI injected object
    
        ...
        $this->deliveryService = $deliveryService;
        ...
    

    public function methodNeedstoBeMocked() 
    
      ....some sql related code...
    

    public function returnToWarehouse($orderId) 
    
      DB::transaction(function() use ($orderId) 
         ...
         $this->deliveryService->someOtherMethod($orderId); // problematic external service call 
         ...
      );
    


现在在我的测试中,我根据this doc link 部分模拟了这个类,我从测试中调用了returnToWarehouse,然后它说

错误:在 null 上调用成员函数 returnToWarehouse()。

表示属性 $deliveryService 在 mock 上不存在。

我的测试实现如下。

    /**
     * @test
     */
    public function an_order_can_be_returned_to_warehouse()
    
        ...
        ...
        $this->partialMock(OrderService::class, function ($mock) 
            $mock->shouldReceive('methodNeedstoBeMocked')->andReturn(collect([]));
        );

        $orderService = app(OrderService::class);

        $orderService->markOrderReturnedToWarehouse($order->id); // here is the problem gets triggered.
        ...
        //assertions
    

这里可能出了什么问题?有什么方法可以缓解这种情况?提前感谢您的帮助。

【问题讨论】:

【参考方案1】:

这里的问题是 Mockery 的部分测试替身不调用原始构造函数。更多信息,请阅读文档here。

或者,您可以考虑以不同的方式模拟“有问题的”方法。例如,您可以将该逻辑提取到 存储库(因为您提到它正在处理数据库层),然后可以在测试期间对其进行模拟。

【讨论】:

【参考方案2】:

通常,当我必须模拟一些第三方服务时,我会像这样设置一个模拟。

这样您可以轻松设置 DI 服务

<?php
if (app()->environment('testing')) 

            $this->app->bind(Delivery::class, static function () 

                $service = \Mockery::mock(Delivery::class);

                $service->shouldReceive('someOtherMethod')->andReturn([]);

                return $service;
            );

        

【讨论】:

但这不是在嘲笑 DeliveryService 吗?我正在做的是嘲笑 OrderService 类。另外建议我把这个放在哪里?在代码库还是测试用例本身? 嗨,我认为您也应该模拟 DeliveryService,但也许我错了,我建议将该代码添加到您的 TestClass 中的 setUp 方法中

以上是关于部分模拟一个类而不影响 PHP 中的私有属性的主要内容,如果未能解决你的问题,请参考以下文章

如何隐藏整个类而不产生错误

为啥同一类而不是同一对象可以访问受保护和私有属性?

如何使用 ConditionExpression 更新一个属性值而不影响其他属性 - DynamoDB

如何只修改 dexie 商店中的一个属性而不删除其余部分?

更新关系数据库中的数据,而不影响已经存在的关系

如何通过样式/主题仅将颜色控制*属性应用于工具栏而不影响其余项目样式