0

I've just tried to write a simple test for Auth:

use Mockery as m;

...

public function testHomeWhenUserIsNotAuthenticatedThenRedirectToWelcome() {
    $auth = m::mock('Illuminate\Auth\AuthManager');
    $auth->shouldReceive('guest')->once()->andReturn(true);

    $this->call('GET', '/');

    $this->assertRedirectedToRoute('general.welcome');
}

public function testHomeWhenUserIsAuthenticatedThenRedirectToDashboard() {
    $auth = m::mock('Illuminate\Auth\AuthManager');
    $auth->shouldReceive('guest')->once()->andReturn(false);

    $this->call('GET', '/');

    $this->assertRedirectedToRoute('dashboard.overview');
}

This is the code:

public function getHome() {
    if(Auth::guest()) {
        return Redirect::route('general.welcome');
    }
    return Redirect::route('dashboard.overview');
}

When I run, I've got the following error:

EF.....

Time: 265 ms, Memory: 13.00Mb

There was 1 error:

1) PagesControllerTest::testHomeWhenUserIsNotAuthenticatedThenRedirectToWelcome
Mockery\Exception\InvalidCountException: Method guest() from Mockery_0_Illuminate_Auth_AuthManager should be called
 exactly 1 times but called 0 times.

—

There was 1 failure:

1) PagesControllerTest::testHomeWhenUserIsAuthenticatedThenRedirectToDashboard
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'http://localhost/dashboard/overview'
+'http://localhost/welcome'

My questions are:

  1. Two similar test cases but why the error output differs? First one the mock Auth::guest() is not called while the second one seems to be called.

  2. On the second test case, why does it fail?

  3. Is there any way to write better tests for my code above? Or even better code to test.

  4. Above test cases, I use Mockery to mock the AuthManager, but if I use the facade Auth::shoudReceive()->once()->andReturn(), then it works eventually. Is there any different between Mockery and Auth::mock facade here?

Thanks.

Pete Houston
  • 14,931
  • 6
  • 47
  • 60

1 Answers1

2

You're actually mocking a new instance of the Illuminate\Auth\AuthManager and not accessing the Auth facade that is being utilized by your function getHome(). Ergo, your mock instance will never get called. (Standard disclaimer that none of the following code is tested.)

Try this:

public function testHomeWhenUserIsNotAuthenticatedThenRedirectToWelcome() {
    Auth::shouldReceive('guest')->once()->andReturn(true);

    $this->call('GET', '/');

    $this->assertRedirectedToRoute('general.welcome');
}

public function testHomeWhenUserIsAuthenticatedThenRedirectToDashboard() {     

    Auth::shouldReceive('guest')->once()->andReturn(false);

    $this->call('GET', '/');

    $this->assertRedirectedToRoute('dashboard.overview');
}

If you check out Illuminate\Support\Facades\Facade, you'll see that it takes care of mocking for you. If you really wanted to do it the way that you were doing it (creating an instance of mock instance of Auth), you'd have to somehow inject it into the code under test. I believe that it could be done with something like this assuming that you extend from the TestCase class provided by laravel:

public function testHomeWhenUserIsNotAuthenticatedThenRedirectToWelcome() {
    $this->app['auth'] = $auth = m::mock('Illuminate\Auth\AuthManager');
    // above line will swap out the 'auth' facade with your facade.

    $auth->shouldReceive('guest')->once()->andReturn(true);

    $this->call('GET', '/');

    $this->assertRedirectedToRoute('general.welcome');
}
awei
  • 1,154
  • 10
  • 26
  • I've just mistaken by not creating the mock object `$this->app->instance()`. Thanks anyway :) – Pete Houston Nov 19 '14 at 16:00
  • Also make sure that if you have `->middleware('auth')` setup on the route that you add this Trait to your tests `Illuminate\Foundation\Testing\WithoutMiddleware` – Etienne Marais Oct 12 '16 at 19:21