Mockery 和 Laravel 构造函数注入

Posted

技术标签:

【中文标题】Mockery 和 Laravel 构造函数注入【英文标题】:Mockery and Laravel constructor injection 【发布时间】:2016-08-11 22:26:10 【问题描述】:

我正在使用带有 php 单元的 laravel 5 来创建一个 laravel 包。我有一个Repository..

namespace Myname\Myapp\Repositories;

use Myname\Myapp\Models\PersonModel;

class PersonRepository

    protected $personModel;

    public function __construct(PersonModel $personModel)
    
        $this->personModel = $personModel;
    

    public function testFunction($var)
    
        return $this->personModel->find($var);
    

..它实现了Model

namespace Myname\Myapp\Models;

use Illuminate\Database\Eloquent\Model;

class PersonModel extends Model

    protected $table = 'person';

Laravel IoC 自动将PersonModel 注入到PersonRepository 的构造函数中。

我正在编写一个单元测试,我想在其中使用 mockery 来模拟 PersonModel 模型,这样我就不会在测试期间访问数据库。

namespace Myname\Myapptests\unit;

use Mockery;

class PersonRepositoryTest extends \Myname\Myapptests\TestCase

     /**
     * @test
     */ 
     public function it_returns_the_test_find()
     
         $mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
            ->shouldReceive('find')
            ->with('var');

         $this->app->instance('Myname\Myapp\Models\PersonModel', $mock);
         $repo = $this->app->make('Myname\Myapp\Repositories\PersonRepository');
         $result = $repo->testFunction('var');

         $this->assert...
     

当我运行测试时出现错误

1) 我的名字\Myapptests\unit\PersonRepositoryTest::it_returns_the_test_find ErrorException: 传递给 Myname\Myapp\Repositories\PersonRepository::__construct() 的参数 1 必须是 Myname\Myapp\Models\PersonModel 的实例,给定的 Mockery\CompositeExpectation 实例

根据我的阅读,mockery 扩展了它正在模拟的类,因此注入扩展类来代替类型提示的父级 (PersonModel) 应该没有问题

显然我错过了一些东西。其他示例将模拟对象显式注入到他们正在测试的类中。 Laravels IoC 正在(应该)为我做这件事。我必须绑定什么吗?

我有一种感觉,虽然模拟对象并没有以我认为的方式创建(扩展 PersonModel),否则我认为我不会看到这个错误。

【问题讨论】:

您可以尝试导入您的模型吗?你正在使用PersonModel::class,但我没有看到use 'Myname\Myapp\Models\PersonModel' @FabioAntunes 更新了代码和错误输出 【参考方案1】:

问题在于您创建模拟的时候:

$mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
    ->shouldReceive('find')
    ->with('var');

所以这个:

$mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
var_dump($mock);
die();

将输出如下内容:Mockery_0_Myname_Myapp_Models_PersonModel

但是这个:

$mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
    ->shouldReceive('find')
    ->with('var');
var_dump($mock);
die();

会输出这个:Mockery\CompositeExpectation

所以尝试做这样的事情:

$mock = Mockery::mock('Myname\Myapp\Models\PersonModel');
$mock->shouldReceive('find')->with('var');

$this->app->instance('Myname\Myapp\Models\PersonModel', $mock);
$repo = $this->app->make('Myname\Myapp\Repositories\PersonRepository');
$result = $repo->testFunction('var');

【讨论】:

@myol 很高兴我帮助了你 只是想我会添加你可以做->with('var')->getMock() 然后可以声明你的模拟和流体链调用以获得实际的模拟对象【参考方案2】:

虽然 Fabio 给出了great answer,但这里的问题实际上是测试设置。 Mockery's mock objects do comply to contracts 并将通过 instanceof 测试并在方法参数中键入提示。

原始代码的问题在于,被调用的方法链最终返回的是期望而不是模拟。相反,我们应该先创建一个模拟,然后向该模拟添加期望。

要修复它,请更改:

$mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
    ->shouldReceive('find')
    ->with('var');

进入这个:

$mock = Mockery::mock('Myname\Myapp\Models\PersonModel');
$mock->shouldReceive('find')->with('var');

变量$mock 现在将实现PersonModel

奖金:

使用PersonModel::class 代替'Myname\Myapp\Models\PersonModel'。这对 IDE 更加友好,可以在以后重构代码时为您提供帮助。

【讨论】:

感谢您的回复,非常有帮助。我觉得我仍然缺少一些东西,因为我的模拟坐席实现了类似 Mockery_0_My_Class_Namespace 的东西......我知道这是一个旧帖子,但也许你可以在这里帮助我,而不是我问同样的问题......$mockCurl = m::mock(HttpRequest::class); $mockCurl->shouldReceive(... RealTag::__construct() 必须是 westonwatson\HttpRequest 的实例或 null,Mockery_0_westonwatson_realtag_HttpRequest 的实例给定 使用PersonModel::class 完全解决了导致我提出这个问题的问题。谢谢!

以上是关于Mockery 和 Laravel 构造函数注入的主要内容,如果未能解决你的问题,请参考以下文章

Laravel Mockery 集成测试

Mockery - 在构造函数中创建一个模拟

如何使用 Mockery 模拟构造函数

laravel中的构造函数依赖注入理解

Laravel 请求:直接在控制器构造函数中注入请求是正确的吗?

如何使用 Laravel IoC 将数据库注入构造函数