Laravel 5 - 使用 Mockery 模拟 Eloquent 模型

Posted

技术标签:

【中文标题】Laravel 5 - 使用 Mockery 模拟 Eloquent 模型【英文标题】:Laravel 5 - Using Mockery to mock Eloquent model 【发布时间】:2016-08-17 20:06:41 【问题描述】:

一直在寻找整个互联网,但似乎没有找到我的问题的答案。我一直在使用 phpUnit 和 Mockery 在 Laravel 中测试控制器。但是,我似乎没有正确模拟基于 Eloquent 的模型。我确实设法以同样的方式模拟了我的 Auth::user(),尽管这在下面的测试中没有使用。

AddressController 中需要测试的函数:

public function edit($id)

    $user = Auth::user();
    $company = Company::where('kvk', $user->kvk)->first();
    $address = Address::whereId($id)->first();

    if(is_null($address)) 
        return abort(404);
    

    return view('pages.address.update')
        ->with(compact('address'));

ControllerTest 包含 setUp 和 mock 方法

abstract class ControllerTest extends TestCase

    /**
     * @var \App\Http\Controllers\Controller
     */
    protected $_controller;

    public function setUp()
        parent::setUp();
        $this->createApplication();
    

    public function tearDown()
    
        parent::tearDown();
        Mockery::close();
    

    protected function mock($class)
    
        $mock = Mockery::mock($class);
        $this->app->instance($class, $mock);
        return $mock;
    

AddressControllerTest 扩展 ControllerTest

class AddressControllerTest extends ControllerTest

    /**
     * @var \App\Models\Address
     */
    private $_address;

    /**
     * @var \App\Http\Controllers\AddressController
     */
    protected $_controller;

    public function setUp()
        parent::setUp();
        $this->_controller = new AddressController();
        $this->_address = factory(Address::class)->make();
    

    public function testEdit404()
        $companyMock = $this->mock(Company::class);
        $companyMock
            ->shouldReceive('where')
            ->with('kvk', Mockery::any())
            ->once();
            ->andReturn(factory(Company::class)->make([
                'address_id' => $this->_address->id
            ]));

        $addressMock = $this->mock(Address::class);
        $addressMock
            ->shouldReceive('whereId')
            ->with($this->_address->id)
            ->once();
            ->andReturn(null);

        //First try to go to route with non existing address
        $this->action('GET', 'AddressController@edit', ['id' => $this->_address->id]);
        $this->assertResponseStatus(404);
    

它不断抛出的错误是:

1) AddressControllerTest::testEdit404
Mockery\Exception\InvalidCountException: Method where("kvk", object(Mockery\Matcher\Any)) from Mockery_1_Genta_Models_Company should be called exactly 1 times but called 0 times.

也许有人有想法?

【问题讨论】:

用 $this->action() 方法替换了 $this->call() 方法,同时检查我的控制器方法是否被调用并且在替换后它确实调用了。但是问题仍然存在。 【参考方案1】:

好的,在找到 Jeffrey Way(Laracasts 背后的人)的多个帖子后,建议人们停止嘲笑 Eloquent 对象,而是在内存数据库中使用,我已经尝试过这种方法。我认为这对于将来遇到相同问题的其他用户可能非常有用,所以我将在下面解释。

现在我已经使用 sqlite 编辑了我的 'config/database.php' 文件以支持内存数据库选项:

'sqlite' => [
            'driver'   => 'sqlite',
            'database' => ':memory:',
            'prefix'   => '',
        ],

接下来在文件顶部您将看到以下配置:

'default' => env('DB_CONNECTION', 'mysql'),

这可以保持不变,这意味着 Laravel 将检查你的 .env 变量以找到一个 'DB_CONNECTION' 或者使用 mysql 作为默认值。这可能是您在像往常一样运行应用程序时想要做的事情。但是,通过测试,您希望覆盖此配置并将数据库配置临时设置为“sqlite”。这可以通过将“DB_CONNECTION”变量添加到您的 .env 文件来完成:

DB_CONNECTION=mysql 

最后在你的 phpunit.xml 中,Laravel 用来实例化单元测试的配置文件,你必须告诉在测试时这个变量应该设置为'sqlite':

<env name="DB_CONNECTION" value="sqlite"/> 

现在你已经完成了,每次你准备进行测试时,Laravel 都会启动一个不可见的内存数据库。不要忘记将以下行添加到需要数据库的测试中。

use \Illuminate\Foundation\Testing\DatabaseMigrations;

它会告诉 Laravel 在开始测试之前运行你的数据库迁移,所以你可以像往常一样使用这些表。

这种方式非常适合我!希望大家可以使用。

【讨论】:

对我来说很好。谢谢。迁移后我也在为数据库播种。只是一个警告:您会发现 @depends testCreateCard 表示法无法传递让我们说从方法到方法的 Id,因为 setUp() 每次测试运行一次,并且每次都会重置数据库。我解决了播种数据并使用硬编码值的问题。

以上是关于Laravel 5 - 使用 Mockery 模拟 Eloquent 模型的主要内容,如果未能解决你的问题,请参考以下文章

在 Laravel 4 中使用 Mockery 模拟自定义类

用于 PHPunit 的 Laravel 5.4 模型模拟

Laravel Mockery:模拟返回逻辑

在 Laravel(流明)上使用 Mockery 模拟 Eloquent 模型不起作用

这个模拟对象上不存在方法 - Laravel,Mockery

用 Mockery 模拟 Laravel Model::increment()