While simplicity is one of the top factor people pick Laravel for, day by day I discover how much Laravel makes tests even simpler, faster and better. If you dig deep into the Laravel community you will find amazing stuff like Test Driven Laravel by Adam Wathan, Testing skill series by Jeffrey Way, and lately Confident Laravel and Tests Generator Shift by Jason McCreary.
Personally, I’m a big fan of the Top-down TDD approach, and I push many developers/teams to use it, but I still feel confused about how to organize my tests, what must I consider feature or unit, and how I should write the tests itself, how can I can consider my tests as a documentation for me and for other developers?
In the past couple of days, I spend most of my time to refactor test suites, and discover some new tips and tricks that make me feel better about my suite especially for the user who works in API driven development world, and hope you will be found them useful as well. I was focused on standardizing things but keeping them extremely simple and easy to adapt.
To demonstrate the suggested standard, I will start with a typical test, and refactor them step by step. Our first test checks if the user can get his/her own categories list:
The test is so simple and straightforward, but I asked myself, why I need to return the $response while I know that it’s a chainable class? I realized that I usually do that because I have custom assertions like the button three lines, or sometimes need to debug the response and dump the JSON.
TestResponse comes with a helper dump function that allows you to debug the JSON in a chainable format, So, the only concern we may have is the custom assertions. Again the TestResponse will support us because it’s a Macroable class, so, I decided to take the fully chainable format by introducing a new Macro.
In this example Passport acting us looks great because it appears right above the get request, but it’s not always the case, and unfortunately, the actingAs helper in the TestCase did not support all passport features especially the scopes out of the box, so I decided to add new method to the TestCase and replace any usage or Passport::acting as to be part of the chainable format.
For me, this looks more readable and not only minimizes the lines and intermediate variable, it also prevents mixing the response related assertions with other assertions if any. But wait, our arrange phase looks wrong!
How we expect the 5 categories to be related to the user, while the user created after them? The first thing any developer will think about is that test is wrong and need to be refactored, but magically he/she will try to run the test and it will pass!
If we aim to write tests for humans as a documentation of the project, not only for CI/CD tools, we need to prevent any magically side effects by clearly mentioning them into the test, most of these scenarios will be in the arrange phase and due to the event-driven development approach. So, how can we solve them?
The first option is very simple and can be used in any case, but will not give us solid dependency between the test cases itself. The second option is absolutely a winner because it allows both developers and machines to know what tests depend on each other, but currently, it only supports single class dependency — a draft pull request in progress to support multi-class dependencies — so you will use a mix of both right now.
In the next article, I will show you another 5 tricks to make your test suite more standardized, readable, maintainable and faster. Finally, if you have a big monolithic application, you may find my Micro TDD series [1,2] useful, and it will help you decompose your application even without adopting the microservices pattern.