使用 find() 模拟 Eloquent 模型
Posted
技术标签:
【中文标题】使用 find() 模拟 Eloquent 模型【英文标题】:Mocking Eloquent Models with find() 【发布时间】:2013-06-05 05:00:57 【问题描述】:我正在尝试用 Mockery 来模拟 Eloquent 模型。模型正在通过
注入控制器__construct(Post $model)$this->model=$model
现在我在控制器中调用find()
函数
$post = $this->model->find($id);
这里是 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));
运行 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
这显然是因为find()
被声明为静态函数。现在,代码可以正常工作,所以我怎样才能成功地模拟 Eloquent 模型而不会失败。由于我们依赖于依赖注入,我必须非静态调用find()
,否则我只能调用Post::find()
。
我想出的一个解决方案是在BaseModel
中创建一个非静态find()
替换
public function nsFind($id, $columns = array('*'))
return self::find($id, $columns);
但这很痛苦,因为函数必须有不同的名称!
是我做错了什么还是你有更好的想法?
【问题讨论】:
This question - 答案实现 Jeferry Way 的答案如下工作 【参考方案1】:Mocking Eloquent 模型是一件非常棘手的事情。它在书中有所介绍,但我特别指出这是一个权宜之计。最好使用存储库。
但是,要回答您的问题,问题是您没有在构造函数中执行模拟。这是让它工作的唯一方法。这并不理想,我不会推荐它。
【讨论】:
所以本书中的代码是否应该工作,因为我已经按照书中所说的那样进行了布局,并且我从本书中概述的 BaseModel 中遇到了类似的错误,我正在尝试使用它进行测试BaseModelTest 我想听听您对此laracasts.com/forum/… Jeffrey 的看法。不是再次使用存储库模式创建***(雄辩)吗?关系呢,使用用户模型的包呢?我只是不明白存储库模式的那一部分。【参考方案2】:我认为这就是原因,Jeffrey 在他的书 Laravel 测试解码(第 10 章)中介绍了存储库。
Mockery 在其 README 中也有关于静态方法的部分,请参阅 https://github.com/padraic/mockery#mocking-public-static-methods
【讨论】:
我正在读这本书(好书)。然而,Jeffrey 没有提及这个问题,在 Jeffrey 的 Generator Helper Package 中,他引入了新的 generate:scaffold artisan 命令来生成 PHPUnit 测试,并且他正在使用这种风格(无存储库)并调用 find() 函数。现在,这些测试也失败了!我看过其他使用 find 方法的资源,没有人提到任何东西!【参考方案3】:有一种方法可以做到这一点。 (Laravel 版本为 5.8)
假设你有一个基类:
<?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);
模型是:
<?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'];
然后是一个服务类,为您提供数据库中的帐户:
<?php
namespace App\Service;
use App\Model\Accounts;
class AccountService
public function getAccountById($id): ?Accounts
return Accounts::find($id);
我们先不讨论这个测试有多么有用,但我相信你会明白它的要点,并且你将不再需要数据库,因为我们在“全局静态”范围内劫持了 find 方法。
然后测试看起来像这样:
<?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();
【讨论】:
以上是关于使用 find() 模拟 Eloquent 模型的主要内容,如果未能解决你的问题,请参考以下文章
Laravel 5 - 使用 Mockery 模拟 Eloquent 模型
Mockery 无法使用 Eloquent 模型填充方法创建部分模拟
使用 Mockery Eloquent 模型模拟 Laravel
如何用 phpunit 模拟 Laravel Eloquent 模型?