Laravel 4 Controller Testing

A Comprehensive Tutorial

Christopher Pitt
Laravel 4 Tutorials

--

Testing is a hot topic these days. Everyone knows that having tests is a good thing, but often they’re either “too busy” to write them, or they just don’t know how.

Even if you do write tests, it may not always be clear what the best way is to prepare for them. Perhaps you’re used to writing them against a particular framework (which I’m assuming is not Laravel 4) or you tend to write fewer when you can’t figure out how exactly to test a particular section of your application.

I have recently spent much time writing tests, and learning spades about how to write them. I have Jeffrey Way to thank, both for Laravel: Testing Decoded and Laracasts.com. These resources have taught me just about everything I know about unit/functional testing.

This article is about some of the practical things you can do to make your code easier to test, and how you would go about writing functional/unit tests against that code. If you take nothing else away, after reading this, you should be subscribed to Laracasts.com and you should read Laravel: Testing Decoded.

Since there’s already so much in the way of testing, I thought it would be better just to focus on the subject of how to write testable controllers, and then how to test that they are doing what they are supposed to be doing.

This tutorial is also different from my others because it doesn’t build a single project, but rather demonstrates general principles which can be used for them all.

While much effort has been spent in the pursuit of accuracy; there’s a good chance you could stumble across a curly quote in a code listing, or some other egregious errata. Please make a note and I will fix where needed.

I have also uploaded this code to Github. You need simply follow the configuration instructions in this tutorial, after downloading the source code, and the application should run fine.

This assumes, of course, that you know how to do that sort of thing. If not; this shouldn’t be the first place you learn about making PHP applications.

https://github.com/formativ/tutorial-laravel-4-testing

If you spot differences between this tutorial and that source code, please raise it here or as a GitHub issue. Your help is greatly appreciated.

Installing Dependencies

For this chapter, we’re going to be using PHPUnit and Mockery. Both of these libraries are used in testing, and can be installed at the same time, by running the following commands:

❯ composer require —dev "phpunit/phpunit:4.0.*"./composer.json has been updated
Loading composer repositories with package information
...
❯ composer require —dev "mockery/mockery:0.9.*"./composer.json has been updated
Loading composer repositories with package information
...

We’ll get into the specifics of each later, but this should at least install them and add them to the require-dev section of your composer.json file.

Unit vs. Functional vs. Acceptance

There are many kinds of tests. There are three which we are going to look at:

  1. Unit
  2. Functional
  3. Acceptance

Unit Tests

Unit tests are tests which cover individual functions or methods. They are meant to run the functions or methods in complete isolation, with no dependencies or side-effects.

You can find a boring description at: http://en.wikipedia.org/wiki/Unit_testing.

Functional Tests

Functional tests are tests which concentrate on the input given to some function or method, and the output returned. They don’t care about isolation or state.

You can find a boring description at: http://en.wikipedia.org/wiki/Functional_testing.

Acceptance Tests

Acceptance tests are test which look at a much broader range of functionality. These are often called end-to-end tests because they are concerned with the correct functionality over a range of functions, methods classes etc.

You can find a boring description at: http://en.wikipedia.org/wiki/Acceptance_testing.

Am I Writing Unit Or Functional Tests?

When it comes to writing tests in Laravel 4, developers often think they are writing unit tests when they are actually writing functional tests. The difference is small but significant.

Laravel provides a set of test classes (a TestCase class, and an ExampleTest class). If you base your tests off of these, you are probably writing functional tests. The TestCase class actually initialises the Laravel 4 framework. If your code depends on that being the case (accessing the aliases, service providers etc.) then your tests aren’t isolated. They have dependencies and they need to be run in order.

When your tests only require the underlying PHPUnit or PHPSpec classes, then you may be writing unit tests.

How does this affect us? Well — if your goal is to write functional tests, and you have decent test coverage then you’re doing ok. But if you want to write true unit tests, then you need to pay attention to how your code is constructed. If you’re using the handy aliases, which Laravel provides, then writing unit tests may be tough.

That should be enough theory to get us started. Let’s take a look at some code…

Fat Controllers

It’s not uncommon to find controllers with actions resembling the following:

public function store()
{
$validator = Validator::make(Input::all(), [
"title" => "required|max:50",
"subtitle" => "required|max:100",
"body" => "required",
"author" => "required|exists:authors"
]);

if ($validator->passes()) {
Posts::create([
"title" => Input::get("title"),
"subtitle" => Input::get("subtitle"),
"body" => Input::get("body"),
"author_id" => Input::get("author"),
"slug" => Str::slug(Input::get("title"))
]);

Mail::send("emails.post", Input::all(), function($email) {
$email
->to("cgpitt@gmail.com", "Chris")
->subject("New post");
});

return Redirect::route("posts.index");
}

return Redirect::back()
->withErrors($validator)
->withInput();
}

This was extracted from app/controllers/PostController.php. I generated the file with the controller:make command.

This is how we first learn to use MVC frameworks, but there comes a point where we are familiar with how they work, and need to start writing tests. Testing this action would be a nightmare. Fortunately, there are a few improvements we can make.

Service Providers

We’ve used service providers to organise and package our code. Now we’re going to use them to help us thin our controllers out. Create a new service provider, resembling the following:

<?php

namespace Formativ;

use Illuminate\Support\ServiceProvider;

class PostServiceProvider
extends ServiceProvider
{
protected $defer = true;

public function register()
{
$this->app->bind(
"Formativ\\PostRepositoryInterface",
"Formativ\\PostRepository"
);

$this->app->bind(
"Formativ\\PostValidatorInterface",
"Formativ\\PostValidator"
);

$this->app->bind(
"Formativ\\PostMailerInterface",
"Formativ\\PostMailer"
);
}

public function provides()
{
return [
"Formativ\\PostRepositoryInterface",
"Formativ\\ValidatorInterface",
"Formativ\\MailerInterface"
];
}
}

This file should be saved as app/Formativ/PostServiceProvider.php.

You will also need to load the Formativ namespace through the composer.json file. You can do that by using either PSR specification, or even through a classmap. More info on that at: https://getcomposer.org/doc/01-basic-usage.md#autoloading.

You can find out more about making service providers at: http://laravel.com/docs/packages.

This does a couple of important things:

  1. Interfaces are connected to concrete implementations, so that we can type-hint the interfaces in our controller, and they will be resolved automatically, with the Laravel IoC container.
  2. We specify which interfaces are provided (in the provides() method) so that we can defer the loading of this service provider until the concrete implementations are called.

We need to define the interfaces and concrete implementations:

<?php

namespace Formativ;

interface PostRepositoryInterface
{
public function all(array $modifiers);
public function first(array $modifiers);
public function insert(array $data);
public function update(array $data, array $modifiers);
public function delete(array $modifiers);
}

This file should be saved as app/Formativ/PostRepositoryInterface.php.

<?php

namespace Formativ;

class PostRepository implements PostRepositoryInterface
{
public function all(array $modifiers)
{
// return all the posts filtered by $modifiers...
}

public function first(array $modifiers)
{
// return the first post filtered by $modifiers...
}

public function insert(array $data)
{
// insert posts with $data...
}

public function update(array $data, array $modifiers)
{
// update posts filtered by $modifiers, with $data...
}

public function delete(array $modifiers)
{
// delete posts filtered by $modifiers...
}
}

This file should be saved as app/Formativ/PostRepository.php.

<?php

namespace Formativ;

interface PostValidatorInterface
{
public function passes($event);
public function messages($event);
public function on($event);
}

This file should be saved as app/Formativ/PostValidatorInterface.php.

<?php

namespace Formativ;

class PostValidator implements PostValidatorInterface
{
public function passes($event)
{
// validate the event instance...
}

public function messages($event)
{
// fetch the error messages for the event instance...
}

public function on($event)
{
// set up the event instance and return it for chaining...
}
}

This file should be saved as app/Formativ/PostValidator.php.

<?php

namespace Formativ;

interface PostMailerInterface
{
public function send($to, $view, $data);
}

This file should be saved as app/Formativ/PostMailerInterface.php.

<?php

namespace Formativ;

class PostMailer implements PostMailerInterface
{
public function send($to, $view, $data)
{
// send an email about the post...
}
}

This file should be saved as app/Formativ/PostMailer.php.

Dependency Injection

With all of these interfaces and concrete implementations in place, we can simply type-hint the interfaces in our controller. This is essentially dependency injection. We don’t create or use dependencies in our controller — rather they are passed in when the controller is instantiated.

The dependencies are resolved automatically, via the IoC container and reflection.

This makes the controller thinner, and helps us break the logic up into a number of smaller, easier-to-test classes:

<?php

use Formativ\PostRepositoryInterface;
use Formativ\PostValidatorInterface;
use Formativ\PostMailerInterface;
use Illuminate\Support\Facades\Response;

class PostController
extends BaseController
{
public function __construct(
PostRepositoryInterface $repository,
PostValidatorInterface $validator,
PostMailerInterface $mailer,
Response $response
)
{
$this->repository = $repository;
$this->validator = $validator;
$this->mailer = $mailer;
$this->response = $response;
}

public function store()
{
if ($this->validator->passes("store"))) {
$this->repository->insert([
"title" => Input::get("title"),
"subtitle" => Input::get("subtitle"),
"body" => Input::get("body"),
"author_id" => Input::get("author"),
"slug" => Str::slug(Input::get("title"))
]);

$this->mailer->send("cgpitt@gmail.com", "emails.post");

return $this->response
->route("posts.index")
->with("success", true);
}

return $this->response
->back()
->withErrors($this->validator->messages("store"))
->withInput();
}

This was extracted from app/controllers/PostController.php.

Another thing you can do, to further modularise your logic, is to dispatch events at critical points in execution:

<?php

use Formativ\PostRepositoryInterface;
use Formativ\PostValidatorInterface;
use Formativ\PostMailerInterface;
use Illuminate\Support\Facades\Response;
use Illuminate\Events\Dispatcher;

class PostController
extends BaseController
{
public function __construct(
PostRepositoryInterface $repository,
PostValidatorInterface $validator,
PostMailerInterface $mailer,
Response $response,
Dispatcher $dispatcher
)
{
$this->repository = $repository;
$this->validator = $validator;
$this->mailer = $mailer;
$this->response = $response;
$this->dispatcher = $dispatcher;

$this->dispatcher->listen(
"post.store",
[$this->repository, "insert"]
);

$this->dispatcher->listen(
"post.store",
[$this->mailer, "send"]
);
}

public function store()
{
if ($this->validator->passes("store")) {
$this->dispatcher->fire("post.store");

return $this->response
->route("posts.index")
->with("success", true);
}

return $this->response
->back()
->withErrors($this->validator->messages("store"))
->withInput();
}

This was extracted from app/controllers/PostController.php.

<?php

namespace Formativ;

use Illuminate\Http\Request;
use Str;

class PostRepository implements PostRepositoryInterface
{
public function __construct(Request $request)
{
$this->request = $request;
}

public function insert()
{
$data = [
"title" => $this->request->get("title"),
"subtitle" => $this->request->get("subtitle"),
"body" => $this->request->get("body"),
"author_id" => $this->request->get("author"),
"slug" => Str::slug($this->request->get("title"))
];

// insert posts with $data...
}

This was extracted from app/Formativ/PostRepository.php.

Using this approach, you can delegate method calls based on events, rather than explicit method calls and variable manipulation.

There are loads of different ways to use events, so you should definitely check out the official docs at: http://laravel.com/docs/events.

This Isn’t Testing!

If you’re wondering how this is related to testing, consider how you would have tested the original controller code. There are many different responsibilities, and places for errors to emerge.

Splitting off your logic into classes of single responsibility is a good thing. Generally following SOLID principles is a good thing. The smaller your classes are, the fewer things each of them do, the easier they are to test.

So how would we write tests for these new classes? Let’s begin with one of the concrete implementations:

<?php

namespace Formativ;

class PostMailerTest
extends TestCase
{
public function testSend()
{
// ...your test here
}
}

This file should be saved as app/tests/Formativ/PostMailerTest.php.

In order for this first test case to run, we’ll need to set up a phpunit config file:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="bootstrap/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<testsuites>
<testsuite name="Application Test Suite">
<directory>./app/tests</directory>
</testsuite>
</testsuites>
</phpunit>

This file should be saved as phpunit.xml.

This will get the tests running, and serves as a template in which to start writing our tests. You can actually see this test case working, by running the following command:

❯ phpunit

PHPUnit 4.0.14 by Sebastian Bergmann.

Configuration read from /path/to/phpunit.xml

.

Time: 76 ms, Memory: 8.75Mb

OK (1 test, 0 assertions)

This test is functional because it only happens after a Laravel instance is spun up, and it doesn’t care about what state it leaves this application instance in.

Since we haven’t implemented the body of the send() method, it’s difficult for us to know what the return value will be. What we can test for is what methods (on the mailer’s underlying mail transport/interface) are being called…

Imagine we’re using the underlying Laravel mail class to send the emails. We used it before we started optimising the controller layer:

Mail::send("emails.post", Input::all(), function($email) {
$email
->to("cgpitt@gmail.com", "Chris")
->subject("New post");
});

You can find out more about the Mailer class at: http://laravel.com/docs/mail.

We’d essentially like to use this logic inside the PostMailer class. We should also dependency-inject our Mail provider:

<?php

namespace Formativ;

use Illuminate\Mail\Mailer;

class PostMailer implements PostMailerInterface
{
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}

public function send($to, $view, $data)
{
$this->mailer->send(
$view, $data,
function($email) use ($to) {
$email->to($to);
}
);
}
}

This file should be saved as app/Formativ/PostMailer.php.

Now we call the send() method on an injected mailer instance, instead of directly on the facade. This is still a little tricky to test (thanks to the callback), but thankfully much easier than if it was still using the facade (and in the controller):

<?php

namespace Formativ;

use Mockery;
use TestCase;

class PostMailerTest
extends TestCase
{
public function tearDown()
{
Mockery::close();
}

public function testSend()
{
$mailerMock = $this->getMailerMock();

$mailerMock
->shouldReceive("send")
->atLeast()->once()
->with(
"bar", ["baz"],
$this->getSendCallbackMock()
);

$postMailer = new PostMailer($mailerMock);
$postMailer->send("foo", "bar", ["baz"]);
}

protected function getSendCallbackMock()
{
return Mockery::on(function($callback) {
$emailMock = Mockery::mock("stdClass");

$emailMock
->shouldReceive("to")
->atLeast()->once()
->with("foo");

$callback($emailMock);

return true;
});
}

protected function getMailerMock()
{
return Mockery::mock("Illuminate\Mail\Mailer");
}
}

This file should be saved as app/tests/Formativ/PostMailerTest.php.

Phew! Let’s break that up so it’s easier to digest…

Mockery::mock("Illuminate\Mail\Mailer");

Part of trying to test in the most isolated manner is substituting dependencies with things that don’t perform any significant function. We do this by creating a new mock instance, via the Mockery::mock() method.

We use this again with:

$emailMock = Mockery::mock("stdClass");

We can use stdClass the second time around because the provided class isn’t type-hinted. Our PostMailer class type-hints the Illuminate\Mail\Mailer class.

We then tell the test to expect that certain methods are called using certain arguments:

$emailMock
->shouldReceive("to")
->atLeast()
->once()
->with("foo");

This tells the mock to expect a call to an as-yet undefined to() method, and to expect that it will be passed “foo” as the first (and single) argument. If your production code expects a stubbed method to return a specific kind of data, you can add the andReturn() method.

We can provide expected callbacks, though it’s slightly trickier:

Mockery::on(function($callback) {
$emailMock = Mockery::mock("stdClass");

$emailMock
->shouldReceive("to")
->atLeast()
->once()
->with("foo");

$callback($emailMock);

return true;
});

We set up a mock, which expects calls to it’s own methods, and then the original callback is run with the provided mock. Don’t forget to add return true — that tells mockery that it’s ok to run the callback with the mock you’ve set up.

At the end of all of this; we’re just testing that methods were called in the correct way. The test doesn’t worry about making sure the Laravel Mailer class actually sends the mail correctly — that has it’s own tests.

The repository class is slightly simpler to test:

<?php

namespace Formativ;

use Mockery;
use TestCase;

class PostRepositoryTest
extends TestCase
{
public function tearDown()
{
Mockery::close();
}

public function testSend()
{
$requestMock = $this->getRequestMock();

$requestMock
->shouldReceive("get")
->atLeast()
->once()
->with("title");

$requestMock
->shouldReceive("get")
->atLeast()
->once()
->with("subtitle");

$requestMock
->shouldReceive("get")
->atLeast()
->once()
->with("body");

$requestMock
->shouldReceive("get")
->atLeast()
->once()
->with("author");

$postRepository = new PostRepository($requestMock);
$postRepository->insert();
}

protected function getRequestMock()
{
return Mockery::mock("Illuminate\Http\Request");
}
}

This file should be saved as app/tests/Formativ/PostRepositoryTest.php.

All we’re doing here is making sure the get method is called four times, on the request dependency. We could extend this to accommodate requests against the underlying database connector object, and the test code would be similar.

We can’t completely test the Str::slug() method because it’s not a facade but rather a static method on the Str class. Every facade allows you to mock methods (facades subclass the MockObject class), and you can even swap them out with your own mocks (using the Validator::swap($validatorMock) method).

You can test static method calls using the AspectMock library, which you can learn more about at: https://github.com/Codeception/AspectMock.

You can learn more about facades at: http://laravel.com/docs/facades.

Finally, let’s test the controller:

<?php

class PostControllerTest
extends TestCase
{
public function tearDown()
{
Mockery::close();
}

public function testConstructor()
{
$repositoryMock = $this->getRepositoryMock();

$mailerMock = $this->getMailerMock();

$dispatcherMock = $this->getDispatcherMock();

$dispatcherMock
->shouldReceive("listen")
->atLeast()
->once()
->with(
"post.store",
[$repositoryMock, "insert"]
);

$dispatcherMock
->shouldReceive("listen")
->atLeast()
->once()
->with(
"post.store",
[$mailerMock, "send"]
);

$postController = new PostController(
$repositoryMock,
$this->getValidatorMock(),
$mailerMock,
$this->getResponseMock(),
$dispatcherMock
);
}

public function testStore()
{
$validatorMock = $this->getValidatorMock();

$validatorMock
->shouldReceive("passes")
->atLeast()
->once()
->with("store")
->andReturn(true);

$responseMock = $this->getResponseMock();

$responseMock
->shouldReceive("route")
->atLeast()
->once()
->with("posts.index")
->andReturn($responseMock);

$responseMock
->shouldReceive("with")
->atLeast()
->once()
->with("success", true);

$dispatcherMock = $this->getDispatcherMock();

$dispatcherMock
->shouldReceive("fire")
->atLeast()
->once()
->with("post.store");

$postController = new PostController(
$this->getRepositoryMock(),
$validatorMock,
$this->getMailerMock(),
$responseMock,
$dispatcherMock
);

$postController->store();
}

public function testStoreFails()
{
$validatorMock = $this->getValidatorMock();

$validatorMock
->shouldReceive("passes")
->atLeast()
->once()
->with("store")
->andReturn(false);

$validatorMock
->shouldReceive("messages")
->atLeast()
->once()
->with("store")
->andReturn(["foo"]);

$responseMock = $this->getResponseMock();

$responseMock
->shouldReceive("back")
->atLeast()
->once()
->andReturn($responseMock);

$responseMock
->shouldReceive("withErrors")
->atLeast()
->once()
->with(["foo"])
->andReturn($responseMock);

$responseMock
->shouldReceive("withInput")
->atLeast()
->once()
->andReturn($responseMock);

$postController = new PostController(
$this->getRepositoryMock(),
$validatorMock,
$this->getMailerMock(),
$responseMock,
$this->getDispatcherMock()
);

$postController->store();
}

protected function getRepositoryMock()
{
return Mockery::mock("Formativ\PostRepositoryInterface")
->makePartial();
}

protected function getValidatorMock()
{
return Mockery::mock("Formativ\PostValidatorInterface")
->makePartial();
}

protected function getMailerMock()
{
return Mockery::mock("Formativ\PostMailerInterface")
->makePartial();
}

protected function getResponseMock()
{
return Mockery::mock("Illuminate\Support\Facades\Response")
->makePartial();
}

protected function getDispatcherMock()
{
return Mockery::mock("Illuminate\Events\Dispatcher")
->makePartial();
}
}

This file should be saved as app/tests/controllers/PostControllerTest.php.

Here we’re still testing method calls, but we also test multiple paths through the store() method.

We haven’t used any assertions, even though they are very useful for unit and functional testing. Feel free to use them to check output values…

You can find out more about Mockery at: https://github.com/padraic/mockery.

You can find out more about PHPUnit at: http://phpunit.de.

The Rabbit Hole

The process of test-writing can take as much time as you want to give it. It’s best just to decide exactly what you need to test, and step away after that.

We’ve just looked at a very narrow area of testing, in our applications. You’re likely to have a richer data layer, and need a ton of tests for that. You’re probably going to want to test the rendering of views.

Don’t think this is an exhaustive reference for how to test, or even that this is the only way to functionally test your controller code. It’s simply a method I’ve found works for the applications I write.

Alternatives

The closest alternative to testing with PHPUnit is probably PHPSpec (http://www.phpspec.net). It uses a similar dialect of assertions and mocking.

If you’re looking to test, in a broader sense, consider looking into Behat (http://behat.org). It uses a descriptive, text-based language to define what behaviour a service/library should have.

Conclusion

If you enjoyed this tutorial; it would be helpful if you could click the Recommend button.

This tutorial comes from a book I’m writing. If you like it and want to support future tutorials; please consider buying it. Half of all sales go to Laravel.

--

--