Question

I am trying to test a controller action. That action should call a function on a model, returning a model. In the test, I mocked the model, bound it to the IoC container. I have the dependency being injected into the controller through its constructor. Yet somehow, the mock isn't being found and called, and instead a live version of the model is being called. (I can tell, as logs are being generated.)

First, my unit test. Create the mock, tell it to expect a function, add it to the IoC container, call the route.

public function testHash(){
    $hash = Mockery::mock('HashLogin');
    $hash->shouldReceive('checkHash')->once();

    $this->app->instance('HashLogin', $hash);

    $this->call('GET', 'login/hash/c3e144adfe8133343b37d0d95f987d87b2d87a24');
}

Second, my controller constructor where the dependency is injected.

public function __construct(User $user, HashLogin $hashlogin){
    $this->user = $user;
    $this->hashlogin = $hashlogin;
    $this->ip_direct = array_key_exists("REMOTE_ADDR",$_SERVER) ? $_SERVER["REMOTE_ADDR"] : null;
    $this->ip_elb = array_key_exists("HTTP_X_FORWARDED_FOR",$_SERVER) ? $_SERVER["HTTP_X_FORWARDED_FOR"] : null;

    $this->beforeFilter(function()
    {
        if(Auth::check()){return Redirect::to('/');}
    });
}

And then my controller method.

public function getHash($code){
    $hash = $this->hashlogin->checkHash($code);
    if(!$hash){
        return $this->badLogin('Invalid Login');
    }
    $user = $this->user->getFromLegacy($hash->getLegacyUser());
    $hash->cleanup();
    $this->login($user);
    return Redirect::intended('/');
}

The controller method is being called correctly, but it seems that it isn't seeing my Mock, so it's calling the actual model's function. This results in the mock's expectations failing, and in checks against the DB that are not desirable.

I am also getting the same problem in another test, though this one uses Laravel's built in Facades.

The test:

public function testLoginSuccessfulWithAuthTrue(){
    Input::shouldReceive('get')->with('username')->once()->andReturn('user');
    Input::shouldReceive('get')->with('password')->once()->andReturn('1234');
    Auth::shouldReceive('attempt')->once()->andReturn(true);
    $user = Mockery::mock('User');
    $user->shouldReceive('buildRBAC')->once();
    Auth::shouldReceive('user')->once()->andReturn($user);

    $this->call('POST', 'login');

    $this->assertRedirectedToRoute('index');
}

The Controller Method:

public function postIndex(){
    $username = Input::get("username");
    $pass = Input::get('password');
    if(Auth::attempt(array('username' => $username, 'password' => $pass))){
        Auth::user()->buildRBAC();
    }else{
        $user = $this->user->checkForLegacyUser($username);
        if($user){
            $this->login($user);
        }else{
            return Redirect::back()->withInput()->with('error', "Invalid credentials.");
        }
    }
    return Redirect::intended('/');
}

I am receiving the error:

Mockery\Exception\InvalidCountException: Method get("username") from Mockery_5_Illuminate_Http_Request should be called exactly 1 times but called 0 times."

Again, I know the method is being called properly, it just seems that mocks aren't being used.

Was it helpful?

Solution

Solved it. I had tried playing with namespacing before in one place or the other, but apparently both the Mockery::mock, and the app->instance() need fully namespaced names. This problem hadn't occurred for me in other tests, so I hadn't even considered it. I hope this helps someone else, because this one racked my brain for a while.

Relevant code fixed:

$hash = Mockery::mock('App\Models\Eloquent\HashLogin');
$this->app->instance('App\Models\Eloquent\HashLogin', $hash);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top