2
votes

I'm developing a PHP (5.4.25) application with laravel(4.2) framework. I'd like test my UserController with Mockery, so I've fit my UserController in this way:

class UsersController extends \BaseController {
    protected $user;

    public function __construct(User $user) {
        $this->user = $user;
        $this->beforeFilter('csrf', array('on'=>'post'));
    }

    public function store() {
        $validator = Validator::make(Input::all(), User::$rules);

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


            return true;
        } else {
            return false;
        }
    }

I want mock Eloquent User model so i develop my UsersControllerTest so:

class UsersControllerTest extends TestCase {

    private $mock;

    public function __construct() {}

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

        $this->createApplication();                                                     
    }

    public function tearDown() {
        parent::tearDown();

        Mockery::close();
    }

    public function testStore() {
        $this->mock = Mockery::mock('Eloquent','User[save]');                                           
        $this->mock
            ->shouldReceive('save')
            ->once()
            ->andReturn('true');                                                        
        $this->app->instance('User', $this->mock);                                      

        $data['username'] = 'qwerety';
        $data['first_name'] = 'asd';
        $data['last_name'] = 'asd123';
        $data['email'] = '[email protected]';
        $data['password'] = 'password';
        $data['password_confirmation'] = 'password';

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

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

} 

When I run my test it returns me this error:

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

Why? What's wrong?

EDIT: I found the problem - If I don't use mock object all works fine and the controller create a new user in the DB, but when I use mock the Input:all() method returns empty array.

-- Thanks

3

3 Answers

2
votes

The way you are testing this, in the controller constructor is passed an instance of the real Eloquent model, not the mock. If you want to test the facades (as clearly stated in the documentation) you should call the shouldReceive() method directly on the facade to have it mocked.

But, since you are redefining the $this->user variable in the store() method of the controller, it will not be called, unless you remove the hardcoded instantiation and use the injected $user.

Edit: i overlooke the $this->app->instance('User', $this->mock); line. So, the problem may be due the fact that in the store method you are getting a new class instance directly, and not via the Laravel IoC container. instead of $this->user = new User; in your store method, you should get it via App::make('User');

2
votes

i had this same issue when i started testing..... the thing there is that, in your userscontroller store method you are actually saving the user to the database and base on your code it might work just fine but you are surprise that it is actually failing the test. Now think of it this way, you mock the user, and you told your test that when ever i call User model, please give me the mock version of my user, like you did in your code line below

$this->app->instance('User', $this->mock);

Now the problem is that you need to also call the mock version of save() method from the through the mock version of User like so:

$this->mock->save();

Consider the following Example:

public function testStore()
{
    $input = ['name', 'Canaan'];

    $this->mock
        ->shouldReceive('create')
        ->once()->with($input);

    $this->app->instance('User', $this->mock);
    $this->mock->create($input);

    $this->call('POST', 'users', $input);

}

i hope it helps you.

1
votes

The problem was in $this->createApplication();.

I have commented that line and Input::all() works fine with all input parameters!