Mockery 和 Laravel 构造函数注入

问题描述 投票:0回答:3

我正在使用 laravel 5 和 php 单元来创建 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';
}

Laravels IoC 自动将

PersonModel
注入到
PersonRepository
的构造函数中。

我正在编写一个单元测试,我想使用模拟来模拟

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),否则我认为我不会看到这个错误。

php laravel phpunit mockery
3个回答
29
投票

问题是当你创建模拟时:

$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');

19
投票

虽然 Fabio 给出了很好的答案,但这里的问题实际上是测试设置。 Mockery 的模拟对象确实遵守契约,并将通过

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

奖金:

不要使用

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


0
投票

我认为在创建模拟时流畅地链接

shouldReceive()
方法是可以的。如果将
getMock()
函数链接到创建模拟的语句末尾,您将返回实例。

所以:

$mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
    ->shouldReceive('find')
    ->with('var')
    ->getMock();
© www.soinside.com 2019 - 2024. All rights reserved.