Frage

After finally getting my stupid simple test to pass, I have a feeling that I'm not doing it correctly.

I have a SessionsController, that is responsible for displaying a login page and logging a user in.

I have decided not to use facades so that I wouldn't have to extend Laravel's TestCase and take a performance hit on my unit tests. Therefore, I have injected all the dependencies through the controller, like so -

SessionsController - Constructor

public function __construct(UserRepositoryInterface $user, 
                            AuthManager $auth, 
                            Redirector $redirect,
                            Environment $view )
{
    $this->user = $user;
    $this->auth = $auth;
    $this->redirect = $redirect; 
    $this->view = $view;
}

I have done the necessary declaring of variables and using the namespaces, which I'm not going to include here as its unnecessary.

the create method detects if a user is authorized, if they are then I redirect them to the home page, otherwise they are displayed the login form.

SessionsController - Create

public function create()
{
    if ($this->auth->user()) return $this->redirect->to('/');

    return $this->view->make('sessions.login');
}

Now for the testing, I'm brand new to it, so bear with me.

SessionsControllerTest

class SessionsControllerTest extends PHPUnit_Framework_TestCase {


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

    public function test_logged_in_user_cannot_see_login_page()
    {
        # Arrange (Create mocked versions of dependencies)

        $user = Mockery::mock('Glenn\Repositories\User\UserRepositoryInterface');

        $authorizedUser = Mockery::mock('Illuminate\Auth\AuthManager');
        $authorizedUser->shouldReceive('user')->once()->andReturn(true);

        $redirect = Mockery::mock('Illuminate\Routing\Redirector');
        $redirect->shouldReceive('to')->once()->andReturn('redirected to home');

        $view = Mockery::mock('Illuminate\View\Environment');


        # Act (Attempt to go to login page)

        $session = new SessionsController($user, $authorizedUser, $redirect, $view);
        $result = $session->create();

        # Assert (Return to home page) 
    }
}

This all passes, but I don't want to have to declare all of these mocked dependencies for each test that I write in my SessionsControllerTest. Is there a way to declare these mocked dependencies once in say a constructor? and then call them by there variables for mocking?

War es hilfreich?

Lösung

You can use the setUp method to declare any dependencies that are global for the entire test class. It's similar to the tearDown method you're currently using:

public function setUp()
{
   // This method will automatically be called prior to any of your test cases
   parent::setUp();

   $this->userMock = Mockery::mock('Glenn\Repositories\User\UserRepositoryInterface');
}

However that won't work if your set up for the mock differs between tests. For this case you can use a helper method:

protected function getAuthMock($isLoggedIn = false)
{
    $authorizedUser = Mockery::mock('Illuminate\Auth\AuthManager');
    $authorizedUser->shouldReceive('user')->once()->andReturn($isLoggedIn);
}

Then when you need the auth mock you can just call getAuthMock. This can greatly simplify your tests.

However

I don't think you're testing your controller correctly. You shouldn't instantiate the controller object yourself, instead you should utilize the call method which exists in Laravel's TestCase class. Try checking out this article about testing Laravel Controllers by Jeffrey Way. I think you're looking to do something more along the lines of this in your test:

class SessionsControllerTest extends TestCase
{
    public function setUp()
    {
        parent::setUp();
    }

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

    public function test_logged_in_user_cannot_see_login_page()
    {
        // This will bind any instances of the Auth manager during 
        // the next request to the mock object returned from the 
        // function below
        App::instance('Illuminate\Auth\Manager', $this->getAuthMock(true));

        // Act
        $this->call('/your/route/to/controller/method', 'GET');

        // Assert
        $this->assertRedirectedTo('/');

    }

    protected function getAuthMock($isLoggedIn)
    {
        $authMock = Mockery::mock('Illuminate\Auth\Manager');
        $authMock->shouldReceive('user')->once()->andReturn($isLoggedIn);
        return $authMock;
    }
}

Andere Tipps

Yes, you can use a "helper". Move the creation of the mocked dependencies into another function, then call that when you need them. Check out slide 52 in this presentation: https://speakerdeck.com/jcarouth/guiding-object-oriented-design-with-tests-1 (well check out the whole thing, but the example is on slide 52)

Edit: The setUp way is even better, I was thinking for something you didn't need in ALL the tests, but I think for what you described doing it in the setUp is way better.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top