Search code examples
laravelmockingphpunitrepository-patternmockery

Laravel 4 - Repository Pattern Testing with PHPUnit and Mockery


I'm working on an application for a client but am having trouble testing the repositories.

To bind the repository to the model I have the following code:

<?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));
        });
    }
}

The repository then extends an abstract class containing the functions from the eloquent ORM (find, where, all etc.). The code for the repository looks something like this:

<?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;
    }
}

To test the calls to the database I'm using Mockery as calls to the database will be too long. Here is my test class:

<?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);
    }
}

As you can see in the test, I'm just trying to call the function and make sure that the functions are chained properly. However I keep getting an error saying:

App\Tests\Unit\Api\FactureSst\FactureSstTest::testFindingBySstAndBdc Mockery\Exception\InvalidCountException: Method where("id_sst", 10) from Mockery_0_FD_Repo_FactureSst_FactureSstInterface should be called exactly 1 times but called 0 times.

Please can somebody help me to understand why this isn't working and how to fix it. Sorry that the code is in French, the application is for a French client.

Thanks in advance.


Solution

  • It looks as though you are mocking the repository itself when you really should be mocking the dependencies of that repository, namely Illuminate\Database\Eloquent\Model which is where you are going to be hitting the database.

    Change setUp() so that it creates a mock object of Illuminate\Database\Eloquent\Model and then have it inject that mock object when your repository is being instantiated.

    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);
    }
    

    This is a little confusing because you state the abstract class contains the ORM methods but when you are calling those methods, you are calling them on the injected Model dependency, not on the abstract class. This is likely where the confusion lies.

    Also, if your models extend Illuminate\Database\Eloquent\Model, it's often better to just inject your model into the repository and not Illuminate\Database\Eloquent\Model. This way, you also get to take advantage of any relationship functions you have setup in your model inside your repository.