Testing API controllers in Laravel
Hello, artisans! I’m Pavel.
I have to cover API methods with tests a lot for different applications and I’ve collected a couple of tricks I would like to tell about.
Preparation
- I use Single Action Controllers when I develop Laravel applications, i.e. a controller has only one action. Below you can see an example.
Controllers
- Users
- UpdateAction.php
- CreateAction.php
- IndexAction.php
- Profile
- UpdatePasswordAction.php
- ...
- News
- IndexAction.php
- MarkAsReadAction.php
- ...
...
This approach allows me to easily navigate between actions and not to have fat controllers. In addition to this controllers can be tested easier than with many actions, because the tests’ structure can repeat the controllers’ structure.
Controllers
- Users
- UpdateActionTest.php
- CreateActionTest.php
- IndexActionTest.php
- Profile
- UpdatePasswordActionTest.php
- ...
- News
- IndexActionTest.php
- MarkAsReadActionTest.php
- ...
...
2. Every action has a unique route name and this name is a single source for URL generations for a specific route.
Let’s start simplifying writing tests
Because I have single action controllers , so every controller can be represented with only one TestCase and we can create auxiliary test case that could help us simplify testing of our controllers. I called in ActionTestCase. The full code of it you can see below.
As you can see this test case has only one required method getRouteName, that should return the name of route for the specific testable controller. A route object will be found by the given name and a test case will get all necessary information about it for making requests.
Here an example of testing user registration
And an example of testing user authorization
As you can see I don’t have fat tests and code duplication.
Couple of words about TestCase methods
assertRouteContainsMiddleware(…$names)
Checking if a route contains given middleware namesassertRouteHasExactMiddleware(…$names)
Checking if a route contains only given middleware names.getRouteByName(): Route
Get a route object. If the route is not found, test would be marked as fail.callRouteAction(array $data = [], array $parameters = [], array $headers = []): TestResponse
Make an unauthorized request.
$data — Request body
$paramenters — Route parameters
$headers — Request headerscallAuthorizedRouteAction(array $data = [], array $parameters = [], array $headers = [], array $scopes = []): TestResponse
Make an authorized request from random user.callAuthorizedByUserRouteAction(User $user, array $data = [], array $parameters = [], array $headers = [], array $scopes = []): TestResponse
Make an authorized request from given user.
That’s all folks!
I hope my story would be useful and may encourage you to write something great. Share your tricks in comments, it will be interesting to read.
P.s. Sorry for my English. I’m trying to write good stories, but I need some time to improve my English. If you notice mistakes, let me know.