I've used the Repository Pattern a few times now, but recently started trying to get better with my phpunit tests. I understand it's not an actual unit test completely because of Eloquent dependencies, but I'm ok with that for now. I'm taking it step by step in learning and applying.
The problem: I can't seem to get a test to pass using mocks and injected dependencies.
Files slimmed down for brevity.
PeopleController.php
<?php
namespace App\Http\Controllers;
use Auth;
use App\Activity;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Contracts\PersonRepositoryInterface;
class PeopleController extends Controller
{
protected $person;
protected $activity;
protected $user;
public function __construct(PersonRepositoryInterface $person, Activity $activity)
{
$this->person = $person;
$this->activity = $activity;
}
public function index()
{
$people = $this->person->getPaginated();
return view('people.index', ['people' => $people]);
}
}
PersonRepositoryInterface.php
<?php
namespace App\Contracts;
interface PersonRepositoryInterface {
public function getPaginated($paginate);
}
PersonRepository.php
<?php
namespace App\Repos;
use App\Person;
use Illuminate\Auth\AuthManager as Auth;
use App\Abstracts\DbRepository;
use App\Contracts\PersonRepositoryInterface;
class PersonRepository extends DbRepository implements PersonRepositoryInterface
{
protected $model;
protected $auth;
public function __construct(Person $model, Auth $auth)
{
$this->model = $model;
$this->auth = $auth;
}
public function getPaginated($pageSize = 20)
{
return $this->model->where([
'team_id' => $this->auth->user()->currentTeam()->id
])->paginate($pageSize);
}
}
PeopleControllerTest.php
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class PeopleControllerTest extends TestCase
{
use WithoutMiddleware;
public function __construct()
{
$this->mock = Mockery::mock('Eloquent', 'App\Person');
}
public function setUp()
{
parent::setUp();
}
public function tearDown()
{
Mockery::close();
}
public function testIndex()
{
$this->mock
->shouldReceive('getPaginated')
->once()
->andReturn('foo');
$this->app->instance('Person', $this->mock);
$auth = Mockery::mock('Illuminate\Auth\AuthManager');
$auth->shouldReceive('user->currentTeam->id')->once()->andReturn('1');
$this->app->instance('Illuminate\Auth\AuthManager', $auth);
$response = $this->call('GET', 'people');
#dd($response->getContent());
$this->assertViewHas('people');
}
}
The problem I'm experiencing at the moment with this code is it fails and when I ...
dd($response->getContent());
... I see it outputs an Exception which contains the following info:
ErrorException in PersonRepository.php line 35:
Undefined property: Mockery_3__demeter_currentTeam::$id
That line on the file is:
'team_id' => $this->auth->user()->currentTeam()->id
I would love understand how to basically mock whatever is being depended upon and get the test to pass.
Thank you for your help in advance.
Ok, solved it finally.
PeopleController.php
<?php
namespace App\Http\Controllers;
use Auth;
use App\Activity;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Contracts\PersonRepositoryInterface;
class PeopleController extends Controller
{
protected $person;
protected $activity;
public function __construct(PersonRepositoryInterface $person, Activity $activity)
{
$this->person = $person;
$this->activity = $activity;
}
public function index()
{
$people = $this->person->getPaginated();
return view('people.index', ['people' => $people]);
}
}
PersonRepositoryInterface.php
<?php
namespace App\Contracts;
interface PersonRepositoryInterface
{
public function getPaginated($paginate = 20);
}
PersonRepository.php
<?php
namespace App\Repos;
use Auth;
use App\Person;
use App\Abstracts\DbRepository;
use App\Contracts\PersonRepositoryInterface;
class PersonRepository extends DbRepository implements PersonRepositoryInterface
{
protected $model;
public function __construct(Person $model)
{
$this->model = $model;
}
public function getPaginated($pageSize = 20)
{
return $this->model->where([
'team_id' => Auth::user()->currentTeam()->id
])->paginate($pageSize);
}
}
PeopleControllerTest.php
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class PeopleControllerTest extends TestCase
{
use WithoutMiddleware;
public function tearDown()
{
Mockery::close();
}
public function setUp()
{
parent::setUp();
$this->mock('App\Contracts\PersonRepositoryInterface');
}
public function mock($class)
{
$this->mock = Mockery::mock($class);
$this->app->instance($class, $this->mock);
return $this->mock;
}
public function testIndex()
{
$this->mock->shouldReceive('getPaginated')->once();
$response = $this->call('GET', 'people');
$this->assertResponseOk();
}
}
There were a couple things that enlightened me...
I don't need to care what is returned back at this point (maybe I will later, but not for now). So, assertResponseOk() and shouldReceive('getPaginated')->once(); should be just fine for the moment.
I needed to mock the interface, not the model, since that's what I'm injecting. Duh. I should have seen that one.
As many have said before, it's ok to not inject Auth into a custom repository function (for now). I may want to do that later when I write tests for the repository itself, though. But for the purposes of this issue/exercise, it wasn't necessary.
You may notice I created an extra little helper function to make future tests in this PeopleControllerTest.php faster/easier, since there's a pattern there. Thanks to Jeffrey Way's old tuts+ post for that one.
Sign in to participate in this thread!
The Laravel portal for problem solving, knowledge sharing and community building.
The community