laravel 使用 find() 模拟 Eloquent 模型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17008637/
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
Mocking Eloquent Models with find()
提问by neyl
I am trying to Mock Eloquent Model with Mockery. Model is being injected in Controller via
我正在尝试用 Mockery 模拟 Eloquent 模型。模型正在通过控制器注入控制器
__construct(Post $model){$this->model=$model}
Now I am calling the find()
function in controller
现在我正在调用find()
控制器中的函数
$post = $this->model->find($id);
And here is test for PostsController
这是 PostsController 的测试
class PostsTest extends TestCase{
protected $mock;
public function setUp() {
parent::setUp();
$this->mock = Mockery::mock('Eloquent', 'Posts'); /*According to Jeffrey Way you have to add Eloquent in this function call to be able to use certain methods from model*/
$this->app->instance('Posts', $this->mock);
}
public function tearDown() {
Mockery::close();
}
public function testGetEdit()
{
$this->mock->shouldReceive('find')->with(1)->once()->andReturn(array('id'=>1));
$this->call('GET', 'admin/posts/edit/1');
$this->assertViewHas('post', array('id'=>1));
}
}
Running PhpUnit gives me error:
运行 PhpUnit 给我错误:
Fatal error: Using $this when not in object context in ...\www\l4\vendor\mockery\mockery\library\Mockery\Generator.php(130) : eval()'d code on line 73
This is obviously because find()
is declared as static function. Now, the code works without errors, so how can I successfully mock Eloquent model without it failing. Since we are relying on dependency injection, I have to call find()
non-statically, otherwise I could just do Post::find()
.
这显然是因为find()
被声明为静态函数。现在,代码可以正常工作,没有错误,那么我如何成功模拟 Eloquent 模型而不会失败。由于我们依赖于依赖注入,我必须find()
非静态地调用,否则我只能做Post::find()
.
One solution that I came up with is to create a non-static find()
replacement in BaseModel
我想出的一个解决方案是find()
在BaseModel
public function nsFind($id, $columns = array('*'))
{
return self::find($id, $columns);
}
But this is a big pain as the function has to have different name!
但这是一个很大的痛苦,因为函数必须有不同的名称!
Am I doing something wrong or do you have any better ideas?
我做错了什么还是你有更好的想法?
回答by JeffreyWay
Mocking Eloquent models is a very tricky thing. It's covered in the book, but I specifically note that it's a stop-gap. It's better to use repositories.
Mocking Eloquent 模型是一件非常棘手的事情。这在书中有介绍,但我特别指出这是一个权宜之计。最好使用存储库。
However, to answer your question, the issue is that you're not performing the mock within the constructor. That's the only way to get it to work. It's not ideal, and I wouldn't recommend it.
但是,要回答您的问题,问题在于您没有在构造函数中执行模拟。这是让它工作的唯一方法。这并不理想,我不会推荐它。
回答by Felix
I think that is the reason, Jeffrey introduces Repositories in his book Laravel Testing Decoded (Chapter 10).
我认为这就是原因,Jeffrey 在他的 Laravel 测试解码(第 10 章)一书中介绍了 Repositories。
Mockery has a section about static methods in its README too, See https://github.com/padraic/mockery#mocking-public-static-methods
Mockery 在其自述文件中也有关于静态方法的部分,请参阅https://github.com/padraic/mockery#mocking-public-static-methods
回答by chickenchilli
There is a way of doing this half decent. (Laravel Version is 5.8)
有一种方法可以做到这一点。(Laravel 版本是 5.8)
Imagine you have a base class:
假设你有一个基类:
<?php
namespace App\Model;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Mockery\Mock;
/**
* Base class of all model classes, to implement Observers or whatever filters you will need
*/
class Model extends EloquentModel
{
protected static $mocks = [];
/**
* @return Mock
*/
public static function getMock()
{
if (isset(self::$mocks[static::class])) {
return self::$mocks[static::class];
}
self::$mocks[static::class] = \Mockery::mock(static::class)->makePartial()->shouldAllowMockingProtectedMethods();
return self::$mocks[static::class];
}
public static function removeMock(): void
{
if (isset(self::$mocks[static::class])) {
unset(self::$mocks[static::class]);
}
}
public static function deleteMocks() : void
{
self::$mocks = [];
}
public static function __callStatic($method, $parameters)
{
/**
* call the mock's function
*/
if (isset(self::$mocks[static::class])) {
return self::$mocks[static::class]->$method(...$parameters);
}
return parent::__callStatic($method, $parameters);
}
}
A model would be:
一个模型是:
<?php
namespace App\Model;
class Accounts extends Model
{
/**
* @var string
*/
protected $table = 'accounts';
/**
* @var string
*/
protected $primaryKey = 'account_id';
/**
* attributes not writable from outside
* @var mixed
*/
protected $guarded = ['account_id'];
}
And then a service class giving you an account from the database:
然后是一个服务类,为您提供一个来自数据库的帐户:
<?php
namespace App\Service;
use App\Model\Accounts;
class AccountService
{
public function getAccountById($id): ?Accounts
{
return Accounts::find($id);
}
}
Let's not go into how useful this test might be, but I am sure you get the gist of it and see that you won't need a database anymore because we hiHyman the find method within a "global static" scope.
让我们不讨论这个测试可能有多大用处,但我相信你明白了它的要点,并看到你将不再需要数据库,因为我们在“全局静态”范围内劫持了 find 方法。
And the test would look something like this then:
然后测试看起来像这样:
<?php
namespace Tests\Unit\Services;
use App\Model\Accounts;
use App\Service\AccountService;
use Tests\TestCase;
class AccountServiceTest extends TestCase
{
public function testGetAccountById()
{
$staticGlobalAccountMock = Accounts::getMock();
$staticGlobalAccountMock->shouldReceive('find')
->andReturn(new Accounts(
['account_id' => 123,
'account_fax' => '056772']));
$service = new AccountService();
$ret = $service->getAccountById(123);
$this->assertEquals('056772',$ret->account_fax);
//clean up all mocks or just this mock
Accounts::deleteMocks();
}
}