Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel testing with PHPUnit and Mockery - Setting up dependencies on Controller test

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?

like image 732
Mitch Avatar asked Apr 29 '14 20:04

Mitch


People also ask

What is mockery Laravel?

When testing Laravel applications, you may wish to "mock" certain aspects of your application so they are not actually executed during a given test. For example, when testing a controller that dispatches an event, you may wish to mock the event listeners so they are not actually executed during the test.

What is PHPUnit testing?

PHPUnit is a unit testing framework for the PHP programming language. It is an instance of the xUnit design for unit testing systems that began with SUnit and became popular with JUnit. Even a small software development project usually takes hours of hard work.

How do I run a single PHPUnit test?

To Run a single PHPUnit Test Case on a method within the file: Open your PHPUnit Test Case file in the editor. Place your cursor on the method you wish to test, right-click and select Run As | PHP Unit Test.

Is PHPUnit a framework?

PHPUnit is a programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks. PHPUnit 9 is the current stable version. PHPUnit 10 is currently in development.


2 Answers

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;
    }
}
like image 68
Jeff Lambert Avatar answered Oct 11 '22 10:10

Jeff Lambert


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.

like image 36
Jessica Avatar answered Oct 11 '22 08:10

Jessica