php - Mockery and Laravel constructor injection
I am using laravel 5 with php unit to create a laravel package. I have aRepository
..
namespace Myname\Myapp\Repositories;
use Myname\Myapp\Models\PersonModel;
class PersonRepository
{
protected $personModel;
public function __construct(PersonModel $personModel)
{
$this->personModel = $personModel;
}
public function testFunction($var)
{
return $this->personModel->find($var);
}
}
..which implements aModel
.
namespace Myname\Myapp\Models;
use Illuminate\Database\Eloquent\Model;
class PersonModel extends Model
{
protected $table = 'person';
}
Laravels IoC automatically injectsPersonModel
into the constructor ofPersonRepository
.
I am writing a unit test where I want to mock thePersonModel
model using mockery so I am not hitting the database during testing.
namespace Myname\Myapptests\unit;
use Mockery;
class PersonRepositoryTest extends \Myname\Myapptests\TestCase
{
/**
* @test
*/
public function it_returns_the_test_find()
{
$mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
->shouldReceive('find')
->with('var');
$this->app->instance('Myname\Myapp\Models\PersonModel', $mock);
$repo = $this->app->make('Myname\Myapp\Repositories\PersonRepository');
$result = $repo->testFunction('var');
$this->assert...
}
}
When I run the test I get an error
1) Myname\Myapptests\unit\PersonRepositoryTest::it_returns_the_test_find ErrorException: Argument 1 passed to Myname\Myapp\Repositories\PersonRepository::__construct() must be an instance of Myname\Myapp\Models\PersonModel, instance of Mockery\CompositeExpectation given
From what I have read, mockery extends the class it is mocking so there should be no issue injecting the extended class in place of the type hinted parent (PersonModel)
Obviously I am missing something. Other examples explicitly inject the mocked object into the class they are then testing. Laravels IoC is (should be) doing this for me. Do I have to bind anything?
I have a feeling though that the mockery object isn't being created in the way I think (extending PersonModel) otherwise I assume I wouldn't see this error.
Answer
Solution:
Problem is when you create your mock:
So this:
Will output something like this:
Mockery_0_Myname_Myapp_Models_PersonModel
But this:
Will output this:
Mockery\CompositeExpectation
So try doing something like this:
Answer
Solution:
While Fabio gives a great answer, the issue here is really the test setup. Mockery's mock objects do comply to contracts and will pass
instanceof
tests and type hints in method arguments.The problem with the original code is that the chain of methods being called end up returning an expectation rather than a mock. We should instead create a mock first, then add expectations to that mock.
To fix it, change this:
Into this:
The variable
$mock
will now implementPersonModel
.Bonus:
Instead of
'Myname\Myapp\Models\PersonModel'
, usePersonModel::class
. This is a lot more IDE-friendly and will help you when refactoring code later on.