Laravel partial mocked model not invoked

2.5k Views Asked by At

I'm using laravel (4.2) framework to develop a web application (PHP 5.4.25). I've create a repository-interface that was implemented with eloquent-repository, I use that repository inside a UserController:

#   app/controllers/UsersController.php

use Gas\Storage\User\UserRepositoryInterface as User;

class UsersController extends \BaseController {
    protected $user;

    public function __construct(User $user) {
        $this->user = $user;

    }

    public function store() {
        $input = Input::all();
        $validator = Validator::make(Input::all(), $this->user->getRoles());

        if ( $validator->passes() ) {
            $this->user->getUser()->username = Input::get('username');
            $this->user->getUser()->password = Hash::make(Input::get('password'));
            $this->user->getUser()->first_name = Input::get('first_name');
            $this->user->getUser()->last_name = Input::get('last_name');
            $this->user->getUser()->email = Input::get('email');
            $this->user->save();


            return false;
        } else {
            return true;
        }
    }
}

My Repository implementation:

namespace Gas\Storage\User;

#   app/lib/Gas/Storage/User/EloquentUserRepository.php

use User;

class EloquentUserRepository implements UserRepositoryInterface {

    public $_eloquentUser;

    public function __construct(User $user) {
        $this->_eloquentUser = $user;
    }

    public function all()
    {
        return User::all();
    }

    public function find($id)
    {
        return User::find($id);
    }

    public function create($input)
    {
        return User::create($input);
    }

    public function save()
    {
        $this->_eloquentUser->save();
    }

    public function getRoles()
    {
        return User::$rules;
    }

    public function getUser()
    {
        return $this->_eloquentUser;
    }
}

I've also create a UsersControllerTest to testing the controller and all works fine, the user was added to the DB. After I mocked my UserRepositoryInterface because I don't need to test the DB insert, but I just want to test the controller

class UsersControllerTest extends TestCase {
    private $mock;

    public function setUp() {
        parent::setUp();
    }

    public function tearDown() {
        Mockery::close();
    }

    public function mock($class) {
        $mock = Mockery::mock($class);

        $this->app->instance($class, $mock);

        return $mock;
    }

    public function testStore() {
        $this->mock = $this->mock('Gas\Storage\User\UserRepositoryInterface[save]');

        $this->mock
            ->shouldReceive('save')
            ->once();

        $data['username'] = 'xxxxxx';
        $data['first_name'] = 'xxxx';
        $data['last_name'] = 'xxxx';
        $data['email'] = '[email protected]';
        $data['password'] = 'password';
        $data['password_confirmation'] = 'password';

        $response = $this->call('POST', 'users', $data);

        var_dump($response->getContent());
    }
}

My ruote file:

Route::resource('users', 'UsersController');

When I run the test I get the following error:

Mockery\Exception\InvalidCountException : Method save() from Mockery_0_Gas_Storage_User_UserRepositoryInterface should be called
 exactly 1 times but called 0 times.

Why the mocked method save has not be called?

What is wrong?


EDIT: without partial mock all works fine, now the question is: why with partial mock it doesn't work?


Thanks

2

There are 2 best solutions below

3
On BEST ANSWER

Looking back at your code, it seems like you should be able to use partial mocks just by changing your mock function to something like this:

public function mock($class) {
    $mock = Mockery::mock($class);
    $ioc_binding = preg_replace('/\[.*\]/', '', $class);
    $this->app->instance($ioc_binding, $mock);

    return $mock;
}
2
On

You are telling the mock to expect the save() method, but the save() is on the Eloquent model inside the Repository, not the Repository you are mocking.

Your code is currently leaking details of the implementation of the Repository.

Instead of calling:

$this->user->getUser()->username = Input::get('username');

You need to pass an instance of the User into the Repository:

$this->user->add(User::create(Input::all());

Or you pass the array of Input into the Repository and allow the Repository to create a new User instance internally:

$this->user->add(Input::all());

You would then mock the add() method in your test:

$this->mock->shouldReceive('add')->once();

The comments about Laravel not being suited for mocking or unit testing are wrong.