Search code examples
phplaravelphpunitmockery

Mockery and Laravel constructor injection


I am using laravel 5 with php unit to create a laravel package. I have a Repository..

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 a Model.

namespace Myname\Myapp\Models;

use Illuminate\Database\Eloquent\Model;

class PersonModel extends Model
{
    protected $table = 'person';
}

Laravels IoC automatically injects PersonModel into the constructor of PersonRepository.

I am writing a unit test where I want to mock the PersonModel 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.


Solution

  • Problem is when you create your mock:

    $mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
        ->shouldReceive('find')
        ->with('var');
    

    So this:

    $mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
    var_dump($mock);
    die();
    

    Will output something like this: Mockery_0_Myname_Myapp_Models_PersonModel

    But this:

    $mock = Mockery::mock('Myname\Myapp\Models\PersonModel')
        ->shouldReceive('find')
        ->with('var');
    var_dump($mock);
    die();
    

    Will output this: Mockery\CompositeExpectation

    So try doing something like this:

    $mock = Mockery::mock('Myname\Myapp\Models\PersonModel');
    $mock->shouldReceive('find')->with('var');
    
    $this->app->instance('Myname\Myapp\Models\PersonModel', $mock);
    $repo = $this->app->make('Myname\Myapp\Repositories\PersonRepository');
    $result = $repo->testFunction('var');