Simple TDD in Laravel with 11 steps
Most web developers cringe when they hear of TDD (test-driven development). Well, I did when I was asked to program with TDD first.
When you are starting off, it feels overwhelming. If you resist it, it will make it harder for you to learn it. So what should you do? Embrace it. Well, it has the reason why it is there. There will always be debates on technology, whether the programming language or the process you will use to develop software.
Some may agree that XP or Xtreme Programming is better than TDD and vise-versa. It will really boil down to which path you would like to take as a programmer especially if you are a team lead. So choose wisely.
Note: This is TDD for API responses. If you want the Feature testing with Laravel blade, head up on this article. If you are into NodeJS, we can also do TDD!
UPDATE: If you want to know about TDD in package development in Laravel, I have written a new article here.
That is too much intro; let’s get to work!
Step 1: PREPARE LARAVEL TEST SUITE
On your root directory, update your phpunit.xml file with:
So it will look like this:
We just need to test in-memory so it will be faster. We will use sqlite database and :memory: for the database. I have to make the debug to false since we need only the actual errors to assert. Increasing memory limit may be needed in the future when your actual calls become expensive.
Make sure your base TestCase if prep for it.
We need to add the DatabaseMigrations trait, so in every run of the test, the migration files are also being run. You may also notice that we have the setUp() and tearDown() methods that are needed to complete that cycle of the application during the test.
STEP 2: WRITE YOUR ACTUAL TEST
Just like what Uncle Bob said, you do not have the authority to write a code (implementation), unless you write the TEST first.
So, let us write our test.
For
PHPUNIT
to know your test, its either you put the/** @test */
annotation on the docblock ortest_
as a prefix.
On this test, we check if we can create an article.
We assert if the application will give us a status 201 and if it will respond with the correct JSON data.
But why do we need to assert the response status FIRST before the response body or payload? It is important because the response status code determines the type of payload we return. If we have 2xx response, we either return the data being asked but if we have 4xx, we return an error response. It should go hand in hand so that the consumer of the API will not be scratching their heads.
For Laravel, since it is using an Active Record ORM Pattern, data are best to persist in the database when created.
After we created our first test, run phpunit
orvendor/bin/phpunit
So when we run phpunit
, it FAILED! Is this good? Yes it is! Because we ran on the second rule of TDD that it should FAIL after creating your test.
Let us examine first why did it fail.
We assert in the test that it should return a 201 but it returned 404. Why?
Most of you might know WHY but for the others, it is because the URL is not yet built. The [POST] /api/v1/articles is not yet existing thus throwing 404.
What do we need to do?
STEP 3: CREATE THE URL IN THE ROUTE FILE
Let us create the URL and see what happens.
Go to your /routes/api.php file and create the URL. When you create url routes in the api.php, it automatically prefixes it with /api.
You can either run:
php artisan make:controller ArticlesApiController —-resource
or you can create it manually. I created it manually. The POST requests goes toArticlesApiController’s
, store() method.
STEP 4: DEBUG YOUR CONTROLLER
So let’s debug and see if the call can reach in that part of our application and run phpunit
again.
It is prompting us the success string in the terminal! This means it is catching our test.
STEP 5: VALIDATE YOUR INPUTS
Do not forget to validate the data going to your database. So we create a new class called CreateArticleRequest to handle validation.
And what does it contain? The validation rules of course!
This is good because we can create later on another test to see if it is catching the validation errors, but that will be on a separate post to make this tutorial simple.
STEP 6: RETURN THE CREATED ARTICLE
Remember that a certain JSON structure should be returned so we know that the data is created in the database. So we need to return the Article object to satisfy our test.
You may notice that the Article class is highlighted. This is because the IDE (PhpStorm) cannot locate that class. So, let’s create it!
STEP 7: CREATE THE ARTICLE CLASS
On this Article class, you have to define the fillable and hidden fields. Once you are done with it, check again your controller and import the Article class.
If you notice, the class is not highlighted anymore since the IDE can locate already the file.
We are getting almost there! The test is set up correctly, the URL is already built and accessible, a controller to catch it is in place as well and the class to model our database tables are ready. Now, let us try to run again the phpunit
.
STEP 8: RUN PHPUNIT AGAIN TO SEE WHAT IS GOING ON
It fails. Again. :( Good or Bad? Well, good and bad. Good because our assertion for a status 201 has changed from 404 to 500 (if you notice that).
And the bad, well, it fails and we need it to pass right? When you want to debug and see what is the application is really throwing, you can do this in your test. Just add ->dump()
method after the post request.
You can further debug the output of our POST request. It might have have all the information you need. If it still doesn’t give you a hint on what is happening, you can rely on the /storage/logs/laravel.log file.
So let us examine why the error has turned into 500.
Well, we are trying to insert data to a table that is not yet existing thus the application is complaining that there is no table to insert the data into.
STEP 9: CREATE THE DATABASE TABLE
You just need to run the laravel command:
php artisan make:migration create_articles_table –create=articles
And it will automagically create a migration files under
/database/migrations
By default, it will only have the ID and the Timestamp fields. It is up to you to fill it up with the required fields.
STEP 10: RUN THE PHPUNIT AGAIN
We’re almost there! We promise this to be 11 steps and we are on step 10! You need to pat your back for making this far!
Ooops, FAILED again? Whyyyyyyy??? But if you examine carefully, you are in the right track! The status code has changed again from 500 to 200. Status 200 is a good sign because it means it is returning something successfully after the POST request! It’s just doesn’t match we need. We need the 201 code to know that an actual post is inserted in the database. So we need to just modify our controller to:
STEP 11: RUN PHPUNIT AND HOPE FOR THE BEST
CONGRATULATIONS! You made it pass and that is the THIRD RULE of Uncle Bob!
This is just the simple implementation of TDD on Laravel. There are other approaches on this that I might cover in the future like the repository pattern. The repository pattern is best implemented with DDD or domain driven development.
###
Edit: You can check the refactor using the repository pattern here.
There are still a lot to cover but this should get your feet wet on TDD. Comments are welcome!
Thank you.
/jsd