Mockery 和 Laravel 构造函数注入

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36723522/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 13:38:02  来源:igfitidea点击:

Mockery and Laravel constructor injection

phplaravelphpunitmockery

提问by myol

I am using laravel 5 with php unit to create a laravel package. I have a Repository..

我正在使用带有 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);
    }
}

..which implements a Model.

.. 实现了一个Model.

namespace Myname\Myapp\Models;

use Illuminate\Database\Eloquent\Model;

class PersonModel extends Model
{
    protected $table = 'person';
}

Laravels IoC automatically injects PersonModelinto the constructor of PersonRepository.

Laravel IoC 自动注入PersonModelPersonRepository.

I am writing a unit test where I want to mock the PersonModelmodel using mockery so I am not hitting the database during testing.

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

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...
     }
}

When I run the test I get an error

当我运行测试时出现错误

1) Myname\Myapptests\unit\PersonRepositoryTest::it_returns_the_test_find ErrorException: Argument 1 passed to Myname\Myapp\Repositories\PersonRepository::__construct() must be an instance of Myname\Myapp\Models\PersonModel, instance of Mockery\CompositeExpectation given

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

From what I have read, mockery extends the class it is mocking so there should be no issue injecting the extended class in place of the type hinted parent (PersonModel)

从我读到的内容来看,mockery 扩展了它正在模拟的类,因此注入扩展类代替类型提示的父类(PersonModel)应该没有问题

Obviously I am missing something. Other examples explicitly inject the mocked object into the class they are then testing. Laravels IoC is (should be) doing this for me. Do I have to bind anything?

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

I have a feeling though that the mockery object isn't being created in the way I think (extending PersonModel) otherwise I assume I wouldn't see this error.

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

回答by Fabio Antunes

Problem is when you create your mock:

问题是当您创建模拟时:

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

So this:

所以这:

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

Will output something like this: Mockery_0_Myname_Myapp_Models_PersonModel

将输出如下内容: Mockery_0_Myname_Myapp_Models_PersonModel

But this:

但是这个:

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

Will output this: Mockery\CompositeExpectation

将输出这个: Mockery\CompositeExpectation

So try doing something like this:

所以尝试做这样的事情:

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

回答by Torkil Johnsen

While Fabio gives a great answer, the issue here is really the test setup. Mockery's mock objects do comply to contractsand will pass instanceoftests and type hints in method arguments.

虽然 Fabio 给出了一个很好的答案,但这里的问题实际上是测试设置。Mockery 的模拟对象确实符合契约,并且会instanceof在方法参数中通过测试和类型提示。

The problem with the original code is that the chain of methods being called end up returning an expectation rather than a mock. We should instead create a mock first, then add expectations to that mock.

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

To fix it, change this:

要修复它,请更改此:

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

Into this:

进入这个:

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

The variable $mockwill now implement PersonModel.

该变量$mock现在将实现PersonModel.

Bonus:

奖金:

Instead of 'Myname\Myapp\Models\PersonModel', use PersonModel::class. This is a lot more IDE-friendly and will help you when refactoring code later on.

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