如何在 Laravel 中对调用另一个模型的方法的模型方法进行单元测试
Posted
技术标签:
【中文标题】如何在 Laravel 中对调用另一个模型的方法的模型方法进行单元测试【英文标题】:How to unit test model method that calls method on another model in Laravel 【发布时间】:2018-05-03 05:51:14 【问题描述】:我是测试和编写可测试代码的新手,我正在寻找有关处理这个简单场景的正确方法的一些说明。我已经阅读了关于 SO 的其他类似标题的问题和答案,但它们似乎没有为我的问题提供明确的答案。
我有一个控制器,它在我的 Picking
类的实例上调用 shipped()
方法:
class MyController extends \BaseController
public function controllerMethod()
$picking = new Picking;
$picking->shipped($shipmentData);
Picking
模型如下所示:
class Picking extends \Eloquent
public function order()
return $this->belongsTo('Order');
public function shipped($shipmentData)
$this->carrier = $shipmentData['Carrier'];
$this->service = $shipmentData['Service'];
$this->is_shipped = true;
$this->save();
$this->order->pickingShipped();
如你所见,这个shipped()
方法保存了一些数据,然后调用pickingShipped()
方法,在它的相关Order
上。
现在,我正在尝试为 shipped()
方法编写测试,但我不确定执行此操作的适当方法。我读过关于模拟的文章,但如果这是需要模拟的情况,我会感到困惑。我已经想到了一些可能的解决方案,但我不确定它们是否正确。
1) 重新排列代码,以便控制器调用pickingShipped()
方法,允许将其从shipped()
方法中删除,从而简化测试。
例如,shipped()
方法的最后一行将被删除,控制器代码将更改为:
$picking = new Picking;
$picking->shipped($shipmentData);
$picking->order->pickingShipped();
2) 在测试中,对order
使用模拟方法,以便测试可以简单地确认pickingShipped()
方法被调用。
类似于here 解释的内容。这意味着测试可以做这样的事情:
$order->expects($this->once())->method('pickingShipped')
但是,我认为这意味着我还需要注入订单依赖项,而不是依赖 shipped()
方法中的 order
关系,如下所示:
class Picking extends \Eloquent
public function order()
return $this->belongsTo('Order');
public function shipped(Order $order, $shipmentData)
$this->carrier = $shipmentData['Carrier'];
$this->service = $shipmentData['Service'];
$this->is_shipped = true;
$this->save();
$order->pickingShipped();
然后控制器中的代码必须如下所示:
$picking = new Picking;
$picking->shipped($picking->order, $shipmentData);
这感觉有点奇怪,但我真的不确定什么是对的。
我的问题是,编写和测试这段代码的正确方法是什么?很容易测试 shipped()
方法在其自身上设置适当的数据,但是最后对 pickingShipped()
的调用呢?这似乎使测试更加复杂。那么代码应该重新排列吗?如果是这样,怎么做?或者,这是我在第二个选项中概述的模拟的常见用例吗?如果是这样,像我展示的那样注入依赖项是否正确?
【问题讨论】:
【参考方案1】:我不是 php 开发人员,所以这可能归结为语言功能成为障碍。
我建议依赖注入方法更好,因为它会调用依赖并允许您稍后将持久性和行为分开。例如Picking
或Picker
可能是更好的行为名称,而PickingRecord
可能更适合数据。
无论如何,如果您可以在 PHP 中设置默认参数,那么我喜欢您使用的最后一种方法(注入),您目前可以简化为类似
public function shipped($shipmentData, Order $order = $this->order)
$this->carrier = $shipmentData['Carrier'];
$this->service = $shipmentData['Service'];
$this->is_shipped = true;
$this->save();
$order->pickingShipped();
这将允许您在生产代码中忽略 order
依赖项,并在测试中将 double 或其他类型的对象作为 order
注入,并简单地断言在 order
对象上调用了该方法。即使您在单元测试中注入双打,集成测试仍应继续监控接口是否仍然啮合在一起。
这就是我尝试在 Ruby 中执行此操作的方式。
【讨论】:
感谢您的反馈!我对这种方法的唯一问题是,我们允许将$order
传递到方法中,而实际上这是不允许的。该方法的重点是它必须在拣货订单上调用pickingShipped
,而不仅仅是任何订单。这意味着我们只是为了测试目的而添加这个参数。仅仅为了测试目的而改变方法签名感觉很奇怪。现在该方法建议它应该用于实际上不应该的行为。
关于默认参数,是的,PHP 允许您传入它们,但它们不能具有像 $this->order
这样的动态值。在这种情况下,我会将默认值设置为null
,然后在方法的开头说if (is_null($order)) $order = $this->order;
,这将完成同样的事情,但就像我上面所说的,这种依赖注入方法仍然感觉不对对我来说。【参考方案2】:
我想出了一个我觉得很好的解决方案。现在我看到它似乎很明显。我所做的只是设置$picking->order
属性以返回测试的模拟顺序。
$order = Mockery::mock(Order::class);
$picking = new Picking;
$picking->order = $order;
$order->shouldReceive('pickingShipped')
->with($picking)
->once();
$picking->shipped($shipmentData);
现在当shipped()
方法调用$this->order
时,它得到了我定义的模拟$order
对象,并且测试正常工作。
这感觉是正确的解决方案。
【讨论】:
以上是关于如何在 Laravel 中对调用另一个模型的方法的模型方法进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章