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 and . 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.
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:
1 ❯ composer require --dev "phpunit/phpunit:4.0.*" 2 3 ./composer.json has been updated 4 Loading composer repositories with package information 5 ... 6 7 ❯ composer require --dev "mockery/mockery:0.9.*" 8 9 ./composer.json has been updated 10 Loading composer repositories with package information 11 ... 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.
There are many kinds of tests. There are three which we are going to look at:
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.
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.
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.
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 base 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…
It’s not uncommon to find controllers with actions resembling the following:
1 public function store() 2 { 3 $validator = Validator::make(Input::all(), [ 4 "title" => "required|max:50", 5 "subtitle" => "required|max:100", 6 "body" => "required", 7 "author" => "required|exists:authors" 8 ]); 9 10 if ($validator->passes()) { 11 Posts::create([ 12 "title" => Input::get("title"), 13 "subtitle" => Input::get("subtitle"), 14 "body" => Input::get("body"), 15 "author_id" => Input::get("author"), 16 "slug" => Str::slug(Input::get("title")) 17 ]); 18 19 Mail::send("emails.post", Input::all(), function($email) { 20 $email 21 ->to("[email protected]", "Chris") 22 ->subject("New post"); 23 }); 24 25 return Redirect::route("posts.index"); 26 } 27 28 return Redirect::back() 29 ->withErrors($validator) 30 ->withInput(); 31 } 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.
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:
1 <?php 2 3 namespace Formativ; 4 5 use Illuminate\Support\ServiceProvider; 6 7 class PostServiceProvider 8 extends ServiceProvider 9 { 10 protected $defer = true; 11 12 public function register() 13 { 14 $this->app->bind( 15 "Formativ\\PostRepositoryInterface", 16 "Formativ\\PostRepository" 17 ); 18 19 $this->app->bind( 20 "Formativ\\PostValidatorInterface", 21 "Formativ\\PostValidator" 22 ); 23 24 $this->app->bind( 25 "Formativ\\PostMailerInterface", 26 "Formativ\\PostMailer" 27 ); 28 } 29 30 public function provides() 31 { 32 return [ 33 "Formativ\\PostRepositoryInterface", 34 "Formativ\\ValidatorInterface", 35 "Formativ\\MailerInterface" 36 ]; 37 } 38 } This does a couple of important things:
We need to define the interfaces and concrete implementations:
1 <?php 2 3 namespace Formativ; 4 5 interface PostRepositoryInterface 6 { 7 public function all(array $modifiers); 8 public function first(array $modifiers); 9 public function insert(array $data); 10 public function update(array $data, array $modifiers); 11 public function delete(array $modifiers); 12 } 1 <?php 2 3 namespace Formativ; 4 5 class PostRepository implements PostRepositoryInterface 6 { 7 public function all(array $modifiers) 8 { 9 // return all the posts filtered by $modifiers... 10 } 11 12 public function first(array $modifiers) 13 { 14 // return the first post filtered by $modifiers... 15 } 16 17 public function insert(array $data) 18 { 19 // insert posts with $data... 20 } 21 22 public function update(array $data, array $modifiers) 23 { 24 // update posts filtered by $modifiers, with $data... 25 } 26 27 public function delete(array $modifiers) 28 { 29 // delete posts filtered by $modifiers... 30 } 31 } 1 <?php 2 3 namespace Formativ; 4 5 interface PostValidatorInterface 6 { 7 public function passes($event); 8 public function messages($event); 9 public function on($event); 10 } 1 <?php 2 3 namespace Formativ; 4 5 class PostValidator implements PostValidatorInterface 6 { 7 public function passes($event) 8 { 9 // validate the event instance... 10 } 11 12 public function messages($event) 13 { 14 // fetch the error messages for the event instance... 15 } 16 17 public function on($event) 18 { 19 // set up the event instance and return it for method chaining... 20 } 21 } 1 <?php 2 3 namespace Formativ; 4 5 interface PostMailerInterface 6 { 7 public function send($to, $view, $data); 8 } 1 <?php 2 3 namespace Formativ; 4 5 class PostMailer implements PostMailerInterface 6 { 7 public function send($to, $view, $data) 8 { 9 // send an email about the post... 10 } 11 } 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.
This makes the controller thinner, and helps us break the logic up into a number of smaller, easier-to-test classes:
1 <?php 2 3 use Formativ\PostRepositoryInterface; 4 use Formativ\PostValidatorInterface; 5 use Formativ\PostMailerInterface; 6 use Illuminate\Support\Facades\Response; 7 8 class PostController 9 extends BaseController 10 { 11 public function __construct( 12 PostRepositoryInterface $repository, 13 PostValidatorInterface $validator, 14 PostMailerInterface $mailer, 15 Response $response 16 ) 17 { 18 $this->repository = $repository; 19 $this->validator = $validator; 20 $this->mailer = $mailer; 21 $this->response = $response; 22 } 23 24 public function store() 25 { 26 if ($this->validator->passes("store"))) { 27 $this->repository->insert([ 28 "title" => Input::get("title"), 29 "subtitle" => Input::get("subtitle"), 30 "body" => Input::get("body"), 31 "author_id" => Input::get("author"), 32 "slug" => Str::slug(Input::get("title")) 33 ]); 34 35 $this->mailer->send("[email protected]", "emails.post"); 36 37 return $this->response 38 ->route("posts.index") 39 ->with("success", true); 40 } 41 42 return $this->response 43 ->back() 44 ->withErrors($this->validator->messages("store")) 45 ->withInput(); 46 } Another thing you can do, to further modularise your logic, is to dispatch events at critical points in execution:
1 <?php 2 3 use Formativ\PostRepositoryInterface; 4 use Formativ\PostValidatorInterface; 5 use Formativ\PostMailerInterface; 6 use Illuminate\Support\Facades\Response; 7 use Illuminate\Events\Dispatcher; 8 9 class PostController 10 extends BaseController 11 { 12 public function __construct( 13 PostRepositoryInterface $repository, 14 PostValidatorInterface $validator, 15 PostMailerInterface $mailer, 16 Response $response, 17 Dispatcher $dispatcher 18 ) 19 { 20 $this->repository = $repository; 21 $this->validator = $validator; 22 $this->mailer = $mailer; 23 $this->response = $response; 24 $this->dispatcher = $dispatcher; 25 26 $this->dispatcher->listen( 27 "post.store", 28 [$this->repository, "insert"] 29 ); 30 31 $this->dispatcher->listen( 32 "post.store", 33 [$this->mailer, "send"] 34 ); 35 } 36 37 public function store() 38 { 39 if ($this->validator->passes("store")) { 40 $this->dispatcher->fire("post.store"); 41 42 return $this->response 43 ->route("posts.index") 44 ->with("success", true); 45 } 46 47 return $this->response 48 ->back() 49 ->withErrors($this->validator->messages("store")) 50 ->withInput(); 51 } 1 <?php 2 3 namespace Formativ; 4 5 use Illuminate\Http\Request; 6 use Str; 7 8 class PostRepository implements PostRepositoryInterface 9 { 10 public function __construct(Request $request) 11 { 12 $this->request = $request; 13 } 14 15 public function insert() 16 { 17 $data = [ 18 "title" => $this->request->get("title"), 19 "subtitle" => $this->request->get("subtitle"), 20 "body" => $this->request->get("body"), 21 "author_id" => $this->request->get("author"), 22 "slug" => Str::slug($this->request->get("title")) 23 ]; 24 25 // insert posts with $data... 26 } Using this approach, you can delegate method calls based on events, rather than explicit method calls and variable manipulation.
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:
1 <?php 2 3 namespace Formativ; 4 5 class PostMailerTest 6 extends TestCase 7 { 8 public function testSend() 9 { 10 // ...your test here 11 } 12 } In order for this first test case to run, we’ll need to set up a phpunit config file:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <phpunit backupGlobals="false" 3 backupStaticAttributes="false" 4 bootstrap="bootstrap/autoload.php" 5 colors="true" 6 convertErrorsToExceptions="true" 7 convertNoticesToExceptions="true" 8 convertWarningsToExceptions="true" 9 processIsolation="false" 10 stopOnFailure="false" 11 syntaxCheck="false" 12 > 13 <testsuites> 14 <testsuite name="Application Test Suite"> 15 <directory>./app/tests/</directory> 16 </testsuite> 17 </testsuites> 18 </phpunit> 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:
1 ❯ phpunit 2 3 PHPUnit 4.0.14 by Sebastian Bergmann. 4 5 Configuration read from /path/to/phpunit.xml 6 7 . 8 9 Time: 76 ms, Memory: 8.75Mb 10 11 OK (1 test, 0 assertions) 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:
1 Mail::send("emails.post", Input::all(), function($email) { 2 $email 3 ->to("[email protected]", "Chris") 4 ->subject("New post"); 5 }); We’d essentially like to use this logic inside the PostMailer class. We should also dependency-inject our Mail provider:
1 <?php 2 3 namespace Formativ; 4 5 use Illuminate\Mail\Mailer; 6 7 class PostMailer implements PostMailerInterface 8 { 9 public function __construct(Mailer $mailer) 10 { 11 $this->mailer = $mailer; 12 } 13 14 public function send($to, $view, $data) 15 { 16 $this->mailer->send( 17 $view, $data, 18 function($email) use ($to) { 19 $email->to($to); 20 } 21 ); 22 } 23 } 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):
1 <?php 2 3 namespace Formativ; 4 5 use Mockery; 6 use TestCase; 7 8 class PostMailerTest 9 extends TestCase 10 { 11 public function tearDown() 12 { 13 Mockery::close(); 14 } 15 16 public function testSend() 17 { 18 $mailerMock = $this->getMailerMock(); 19 20 $mailerMock 21 ->shouldReceive("send") 22 ->atLeast()->once() 23 ->with( 24 "bar", ["baz"], 25 $this->getSendCallbackMock() 26 ); 27 28 $postMailer = new PostMailer($mailerMock); 29 $postMailer->send("foo", "bar", ["baz"]); 30 } 31 32 protected function getSendCallbackMock() 33 { 34 return Mockery::on(function($callback) { 35 $emailMock = Mockery::mock("stdClass"); 36 37 $emailMock 38 ->shouldReceive("to") 39 ->atLeast()->once() 40 ->with("foo"); 41 42 $callback($emailMock); 43 44 return true; 45 }); 46 } 47 48 protected function getMailerMock() 49 { 50 return Mockery::mock("Illuminate\Mail\Mailer"); 51 } 52 } Phew! Let’s break that up so it’s easier to digest…
1 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:
1 $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 IlluminateMailMailer class.
We then tell the test to expect that certain methods are called using certain arguments:
1 $emailMock 2 ->shouldReceive("to") 3 ->atLeast() 4 ->once() 5 ->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:
1 Mockery::on(function($callback) { 2 $emailMock = Mockery::mock("stdClass"); 3 4 $emailMock 5 ->shouldReceive("to") 6 ->atLeast() 7 ->once() 8 ->with("foo"); 9 10 $callback($emailMock); 11 12 return true; 13 }); 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:
1 <?php 2 3 namespace Formativ; 4 5 use Mockery; 6 use TestCase; 7 8 class PostRepositoryTest 9 extends TestCase 10 { 11 public function tearDown() 12 { 13 Mockery::close(); 14 } 15 16 public function testSend() 17 { 18 $requestMock = $this->getRequestMock(); 19 20 $requestMock 21 ->shouldReceive("get") 22 ->atLeast() 23 ->once() 24 ->with("title"); 25 26 $requestMock 27 ->shouldReceive("get") 28 ->atLeast() 29 ->once() 30 ->with("subtitle"); 31 32 $requestMock 33 ->shouldReceive("get") 34 ->atLeast() 35 ->once() 36 ->with("body"); 37 38 $requestMock 39 ->shouldReceive("get") 40 ->atLeast() 41 ->once() 42 ->with("author"); 43 44 $postRepository = new PostRepository($requestMock); 45 $postRepository->insert(); 46 } 47 48 protected function getRequestMock() 49 { 50 return Mockery::mock("Illuminate\Http\Request"); 51 } 52 } 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 water 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).
Finally, let’s test the controller:
1 <?php 2 3 class PostControllerTest 4 extends TestCase 5 { 6 public function tearDown() 7 { 8 Mockery::close(); 9 } 10 11 public function testConstructor() 12 { 13 $repositoryMock = $this->getRepositoryMock(); 14 15 $mailerMock = $this->getMailerMock(); 16 17 $dispatcherMock = $this->getDispatcherMock(); 18 19 $dispatcherMock 20 ->shouldReceive("listen") 21 ->atLeast() 22 ->once() 23 ->with( 24 "post.store", 25 [$repositoryMock, "insert"] 26 ); 27 28 $dispatcherMock 29 ->shouldReceive("listen") 30 ->atLeast() 31 ->once() 32 ->with( 33 "post.store", 34 [$mailerMock, "send"] 35 ); 36 37 $postController = new PostController( 38 $repositoryMock, 39 $this->getValidatorMock(), 40 $mailerMock, 41 $this->getResponseMock(), 42 $dispatcherMock 43 ); 44 } 45 46 public function testStore() 47 { 48 $validatorMock = $this->getValidatorMock(); 49 50 $validatorMock 51 ->shouldReceive("passes") 52 ->atLeast() 53 ->once() 54 ->with("store") 55 ->andReturn(true); 56 57 $responseMock = $this->getResponseMock(); 58 59 $responseMock 60 ->shouldReceive("route") 61 ->atLeast() 62 ->once() 63 ->with("posts.index") 64 ->andReturn($responseMock); 65 66 $responseMock 67 ->shouldReceive("with") 68 ->atLeast() 69 ->once() 70 ->with("success", true); 71 72 $dispatcherMock = $this->getDispatcherMock(); 73 74 $dispatcherMock 75 ->shouldReceive("fire") 76 ->atLeast() 77 ->once() 78 ->with("post.store"); 79 80 $postController = new PostController( 81 $this->getRepositoryMock(), 82 $validatorMock, 83 $this->getMailerMock(), 84 $responseMock, 85 $dispatcherMock 86 ); 87 88 $postController->store(); 89 } 90 91 public function testStoreFails() 92 { 93 $validatorMock = $this->getValidatorMock(); 94 95 $validatorMock 96 ->shouldReceive("passes") 97 ->atLeast() 98 ->once() 99 ->with("store") 100 ->andReturn(false); 101 102 $validatorMock 103 ->shouldReceive("messages") 104 ->atLeast() 105 ->once() 106 ->with("store") 107 ->andReturn(["foo"]); 108 109 $responseMock = $this->getResponseMock(); 110 111 $responseMock 112 ->shouldReceive("back") 113 ->atLeast() 114 ->once() 115 ->andReturn($responseMock); 116 117 $responseMock 118 ->shouldReceive("withErrors") 119 ->atLeast() 120 ->once() 121 ->with(["foo"]) 122 ->andReturn($responseMock); 123 124 $responseMock 125 ->shouldReceive("withInput") 126 ->atLeast() 127 ->once() 128 ->andReturn($responseMock); 129 130 $postController = new PostController( 131 $this->getRepositoryMock(), 132 $validatorMock, 133 $this->getMailerMock(), 134 $responseMock, 135 $this->getDispatcherMock() 136 ); 137 138 $postController->store(); 139 } 140 141 protected function getRepositoryMock() 142 { 143 return Mockery::mock("Formativ\PostRepositoryInterface") 144 ->makePartial(); 145 } 146 147 protected function getValidatorMock() 148 { 149 return Mockery::mock("Formativ\PostValidatorInterface") 150 ->makePartial(); 151 } 152 153 protected function getMailerMock() 154 { 155 return Mockery::mock("Formativ\PostMailerInterface") 156 ->makePartial(); 157 } 158 159 protected function getResponseMock() 160 { 161 return Mockery::mock("Illuminate\Support\Facades\Response") 162 ->makePartial(); 163 } 164 165 protected function getDispatcherMock() 166 { 167 return Mockery::mock("Illuminate\Events\Dispatcher") 168 ->makePartial(); 169 } 170 } Here we’re still testing method calls, but we also test multiple paths through the store() method.
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.
The closest alternative to testing with PHPUnit is probably PHPSpec (). It uses a similar dialect of assertions and mocking.
If you’re looking to test, in a broader sense, consider looking into Behat (). It uses a descriptive, text-based language to define what behaviour a service/library should have.