cult3

How to structure testable Controllers in Laravel 4

Jul 15, 2013

Table of contents:

  1. What should I be testing in my Controller?
  2. An example of bad practice
  3. Dependency Injection to the rescue
  4. Automatic Resolution
  5. Injecting the database into a Controller
  6. Mocking the database in your Controller tests
  7. Mocking with Mockery
  8. Setting up Mockery
  9. Writing your first Controller test
  10. Conclusion

In last week’s post I looked at using Repositories in Laravel 4 to abstract the database layer away from the Controller. This enables you to easily switch out the type of database or the specific ORM that you want to use without having to change any of your Controller code in your actual application.

This week I want to look at how you should structure your Controllers to make them testable. Testing your Controllers is a critical aspect of building a solid web application, but it is important that you only tests the appropriate bits of your application.

Fortunately, Laravel 4 makes separating the concerns of your Controller really easy. This makes testing your Controllers really straight forward as long as you have structured them correctly.

So let’s get started!

What should I be testing in my Controller?

Before I get into how to structure your Controllers for testability, first its important to understand what exactly we need to test for.

As I mentioned in Setting up your first Laravel 4 Controller, Controllers should only be concerned with moving data between the Model and the View. You don’t need to verify that the database is pulling the correct data, only that the Controller is calling the right method. Therefore your Controller tests should never touch the database.

This is really what I’m going to be showing you today because by default it is pretty easy to slip into coupling the Controller and the Model together.

An example of bad practice

As a way of illustrating what I’m trying to avoid, here is an example of a Controller method:

public function index()
{
    return User::all();
}

This is a bad practice because we have no way of mocking User::all(); and so the associated test will be forced to hit the database.

Dependency Injection to the rescue

In order to get around this problem, we have to inject the dependency into the Controller. Dependency Injection is where you pass the class an instance of an object, rather than letting that object create the instance for its self.

By injecting the dependency into the Controller, we can pass the class a mock instead of the database instead of the actual database object itself during our tests. This means we can test the functionality of the Controller without ever touching the database.

As a general guide, anywhere you see a class that is creating an instance of another object it is usually a sign that this could be handled better with dependency injection. You never want your objects to be tightly coupled and so by not allowing a class to instantiate another class you can prevent this from happening.

Automatic Resolution

Laravel 4 has a beautiful way of handling Dependency Injection. This means you can resolve classes without any configuration at all in many scenarios.

This means that if you pass a class an instance of another class through the constructor, Laravel will automatically inject that dependency for you!

Basically, everything will work without any configuration on your part.

Injecting the database into a Controller

So now you understand the problem and the theory of the solution, we can now fix the Controller so it isn’t coupled to the database.

If you remember back to last week’s post on Laravel Repositories, you might have noticed that I already fixed this problem.

So instead of doing:

public function index()
{
    return User::all();
}

I did:

public function __construct(User $user)
{
    $this->user = $user;
}

/**
 * Display a listing of the resource.
 *
 * @return Response
 */
public function index()
{
    return $this->user->all();
}

When the UserController class is created, the __construct method is automatically run. The __construct method is injected with an instance of the User repository, which is then set on the $this->user property of the class.

Now whenever you want to use the database in your methods, you can use the $this->user instance.

Mocking the database in your Controller tests

The real magic happens when you come to write your Controller tests. Now that you are passing an instance of the database to the Controller, you can mock the database instead of actually hitting the database. This will not only improve performance, but you won’t have any test data lying around after your tests.

First thing I’m going to do is to create a new folder under the tests directory called functional. I like to think of Controller tests as being functional tests because we are testing the incoming traffic and the rendered view.

Next I’m going to create a file called UserControllerTest.php and write the following boilerplate code:

class UserControllerTest extends TestCase
{
}

Mocking with Mockery

If you remember back to my post, What is Test Driven Development?, I talked about Mocks as being, a replacement for dependent objects.

In order to create Mocks for the tests in Cribbb, I’m going to use a fantastic package called Mockery.

Mockery allows you to mock objects in your project so you don’t have to use the real dependency. By mocking an object, you can tell Mockery which method you would like to call and what you would like to be returned.

This enables you to isolate your dependencies so you only make the required Controller calls in order for the test to pass.

For example, if you wanted to call the all() method on your database object, instead of actually hitting the database you can mock the call by telling Mockery you want to call the all() method and it should return an expected value. You aren’t testing whether the database can return records or not, you only care about being able to trigger the method and deal with the return value.

Installing Mockery Like all good PHP packages, Mockery can be installed through Composer.

To install Mockery through Composer, add the following line to your composer.json file:

{
    "require-dev": {
        "mockery/mockery": "dev-master"
    }
}

Next, install the package:

composer install -dev

Setting up Mockery

Now to set up Mockery, we have to create a couple of set up methods in the test file:

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

    $this->mock = $this->mock('Cribbb\Storage\User\UserRepository');
}

public function mock($class)
{
    $mock = Mockery::mock($class);

    $this->app->instance($class, $mock);

    return $mock;
}

The setUp() method is run before any of the tests. Here we are grabbing a copy of the UserRepository and creating a new mock.

In the mock() method, $this->app->instance tells Laravel’s IoC container to bind the $mock instance to the UserRepository class. This means that whenever Laravel wants to use this class, it will use the mock instead.

Writing your first Controller test

Next you can write your first Controller test:

public function testIndex()
{
    $this->mock->shouldReceive('all')->once();

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

    $this->assertResponseOk();
}

In this test I’m asking the mock to call the all() method once on the UserRepository. I then call the page using a GET request and then I assert that the response was ok.

Conclusion

Testing Controllers shouldn’t be as difficult or as complicated as it is made out to be. As long as you isolate the dependencies and only test the right bits, testing Controllers should be really straight forward.

In next week’s tutorial, I’m going to take a much deeper look at using Mockery in your tests. Mockery is an essential package for writing good applications and it is so powerful it deserves a post dedicated to it.

This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.