我正试图用Mockery模拟Eloquent模型。模型正在通过
注入Controller__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()
。
我提出的一个解决方案是在find()
BaseModel
替换
public function nsFind($id, $columns = array('*'))
{
return self::find($id, $columns);
}
但这是一个很大的痛苦,因为这个功能必须有不同的名字!
我做错了什么或你有更好的想法吗?
答案 0 :(得分:5)
嘲弄雄辩的模型是一件非常棘手的事情。本书对此进行了介绍,但我特别指出,这是一个止损。最好使用存储库。
但是,要回答您的问题,问题是您没有在构造函数中执行模拟。这是让它发挥作用的唯一途径。这不太理想,我不推荐它。
答案 1 :(得分:2)
我认为这就是原因,Jeffrey在他的Laradvel Testing Decoded(第10章)一书中介绍了存储库。
Mockery在其自述文件中也有关于静态方法的部分,请参阅https://github.com/padraic/mockery#mocking-public-static-methods
答案 2 :(得分:1)
有一种方法可以做到这一点。 (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方法。 / p>
然后测试将如下所示:
<?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();
}
}