【问题标题】:Laravel 4 - Repository Pattern Testing with PHPUnit and MockeryLaravel 4 - 使用 PHPUnit 和 Mockery 进行存储库模式测试
【发布时间】:2015-06-15 22:24:47
【问题描述】:

我正在为客户开发一个应用程序,但在测试存储库时遇到问题。

要将存储库绑定到模型,我有以下代码:

<?php

namespace FD\Repo;

use App;
use Config;

/**
 * Service Provider for Repository
 */
class RepoServiceProvider extends \Illuminate\Support\ServiceProvider
{
    public function register()
    {
        $app = $this->app;

        $app->bind('FD\Repo\FactureSst\FactureSstInterface', function ($app) {
            return new FactureSst\EloquentFactureSst(App::make('FactureSst'), new \FD\Service\Cache\LaravelCache($app['cache'], 'factures_sst', 10));
        });
    }
}

然后,存储库扩展了一个抽象类,其中包含来自 eloquent ORM 的函数(查找、位置、所有等)。存储库的代码如下所示:

<?php

namespace FD\Repo\FactureSst;

use Illuminate\Database\Eloquent\Model;
use FD\Repo\AbstractBaseRepo;
use FD\Repo\BaseRepositoryInterface;
use FD\Service\Cache\CacheInterface;
use Illuminate\Support\Collection;

class EloquentFactureSst extends AbstractBaseRepo implements BaseRepositoryInterface, FactureSstInterface
{
    protected $model;
    protected $cache;

    public function __construct(Model $resource, CacheInterface $cache)
    {
        $this->model = $resource;
        $this->cache = $cache;
    }

    /**
     * Retrieve factures with the given SST and BDC IDs.
     *
     * @param int $sst_id
     * @param int $bdc_id
     * @return \Illuminate\Support\Collection
     */
    public function findWithSstAndBdc($sst_id, $bdc_id)
    {
        $return = new Collection;

        $factures = $this->model->where('id_sst', $sst_id)
            ->whereHas('facture_assoc', function ($query) use ($bdc_id) {
                $query->where('id_bdc', $bdc_id);
            })
            ->get();

        $factures->each(function ($facture) use (&$return) {
            $data = [
                'facture_id'   => $facture->id,
                'facture_name' => $facture->num_facture,
                'total_dsp'    => $facture->total_dsp(),
                'total_tradi'  => $facture->total_tradi()
            ];

            $return->push($data);
        });

        return $return;
    }
}

为了测试对数据库的调用,我使用了 Mockery,因为对数据库的调用会太长。这是我的测试类:

<?php namespace App\Tests\Unit\Api\FactureSst;

use App;
use FactureSst;
use Illuminate\Database\Eloquent\Collection;
use Mockery as m;
use App\Tests\FdTestCase;

class FactureSstTest extends FdTestCase
{
    /**
     * The primary repository to test.
     */
    protected $repo;

    /**
     * Mocked version of the primary repo.
     */
    protected $mock;

    public function setUp()
    {
        parent::setUp();
        $this->repo = App::make('FD\Repo\FactureSst\FactureSstInterface');
        $this->mock = $this->mock('FD\Repo\FactureSst\FactureSstInterface');
    }

    public function tearDown()
    {
        parent::tearDown();
        m::close();
    }

    public function mock($class)
    {
        $mock = m::mock($class);
        $this->app->instance($class, $mock);
        return $mock;
    }

    public function testFindingBySstAndBdc()
    {
        $this->mock->shouldReceive('where')->with('id_sst', 10)->once()->andReturn($this->mock);
        $this->mock->shouldReceive('whereHas')->with('facture_assoc')->once()->andReturn($this->mock);
        $this->mock->shouldReceive('get');

        $result = $this->repo->findWithSstAndBdc(30207, 10);
        $this->assertEquals($result, new \Illuminate\Support\Collection);
        $this->assertEquals($result->count(), 0);
    }
}

正如您在测试中看到的那样,我只是尝试调用函数并确保函数正确链接。但是我不断收到错误消息:

App\Tests\Unit\Api\FactureSst\FactureSstTest::testFindingBySstAndBdc Mockery\Exception\InvalidCountException:应调用 Mockery_0_FD_Repo_FactureSst_FactureSstInterface 中的方法 where("id_sst", 10) 正好 1 次,但调用了 0 次。

请有人帮我理解为什么这不起作用以及如何解决它。抱歉,代码是法语的,该应用程序是针对法语客户端的。

提前致谢。

【问题讨论】:

    标签: laravel mocking phpunit repository-pattern mockery


    【解决方案1】:

    当您真正应该模拟该存储库的依赖项时,您似乎在模拟存储库本身,即Illuminate\Database\Eloquent\Model,这是您将要访问数据库的位置。

    更改setUp(),使其创建Illuminate\Database\Eloquent\Model 的模拟对象,然后在实例化您的存储库时注入该模拟对象。

    public function setUp()
    {
        parent::setUp();
        $this->mock = m::mock('Illuminate\Database\Eloquent\Model');  // Or better if you mock 'FactureSst'
        $this->app->instance('FactureSst', $this->mock);
    }
    

    这有点令人困惑,因为您声明抽象类包含 ORM 方法,但是当您调用这些方法时,您是在注入的 Model 依赖项上调用它们,而不是在抽象类上。这很可能是混乱所在。

    此外,如果您的模型扩展 Illuminate\Database\Eloquent\Model,通常最好将模型注入存储库而不是 Illuminate\Database\Eloquent\Model。这样,您还可以利用您在存储库中的模型中设置的任何关系函数。

    【讨论】:

    • 您好,感谢您的回复。正如你所说,我有一个包含 ORM 方法的抽象类,所以我应该调用我必须更改的模型。周二上班的时候去看看,谢谢回复
    • 刚刚检查出来,通过调用抽象 repo 方法而不是模型,测试正在工作。非常感谢
    【解决方案2】:

    如果我没记错这一行

    $result = $this->repo->findWithSstAndBdc(30207, 10);
    

    应该是

    $result = $this->mock->findWithSstAndBdc(30207, 10);
    

    另外请记住,您可以模拟整个调用链(对这样的流畅查询很有用):

    $this->mock->shouldReceive('where->whereHas->get')
         ->once()->andReturn(/*MAKE A FAKE OBJECT HERE*/);
    

    我也会使用 PHP 的内置 foreach() 而不是使用 $factures->each,因为这样可以少做一件测试。

    【讨论】:

    • 您好,感谢您的回复。正如建议的那样,我尝试用 $this->mock 替换 $this->repo 但我现在收到以下错误:BadMethodCallException: Method Mockery_0_FD_Repo_FactureSst_FactureSstInterface::findWithSstAndBdc() 在这个模拟对象上不存在
    猜你喜欢
    • 2015-01-27
    • 2016-04-25
    • 2017-08-16
    • 2014-03-21
    • 2014-06-15
    • 2013-09-13
    • 2019-10-24
    • 2016-06-04
    • 2017-08-16
    相关资源
    最近更新 更多