Laravel 5 - 使用Mockery模拟Eloquent模型

时间:2016-04-24 13:09:22

标签: php unit-testing laravel eloquent mockery

一直在寻找互联网,但似乎没有找到我的问题的答案。我一直在使用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'));
}

包含setUp和mock方法的ControllerTest

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.

也许有人有想法?

1 个答案:

答案 0 :(得分:7)

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

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

'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在开始测试之前运行数据库迁移,因此您可以像平常一样使用这些表。

这种方式对我来说非常合适!希望你们能用它。