Laravel: Testing Database Driven applications with PHPUnit Part 3
In part 2 we learned how to test CRUD operations to manage customer data. In this tutorial of our four part series, we are going to continue testing and developing our banking application. The tests we’re going to create in this section will help ensure that our application can issue a loan to customer, calculate loan interest amount and amount payable correctly and view loan report.
To start off, we’ll create a unit test to test loan interest rate and the amount payable calculation. As we all know the formula to calculate interest is I=PxRxT where I stands for Interest, P for Principle and T for Time.
Execute the following command to create the unit test.
php artisan make:test LoanCalculatorTest --unit
LoanCalculatorTest class will be created under tests/Unit folder path.
Notice all other tests we created previously were saved under tests/Feature folder path. This is because these were feature tests testing major functionalities of the application such as CRUD operations as we have seen in previous tutorials. But for the loan calculator test we added — unit flag so as to create it as unit test. Unit tests test the smallest parts of an application called units such as calculations as we’re going to see in a while.
Open LoanCalculatorTest class and update code as shown below:
We’re going to put our loan calculation logic in separate class to make the code more maintainable and testable. Thus if look keenly you’ll notice that our test class is testing the LoanCalculator class which is supposed to be in app\Services folder path. There are two methods being tested in loan calculator class, that’s calculateInterestAmount and calculateAmountPayable. The two methods calculates loan interest amount and loan amount payable respectively.
Notice that to provide test data to test the calculations we’re using a Data Provider. To learn more about data providers see PHPUnit docs.
Run the test by executing the command below:
php artisan test --filter=LoanCalculatorTest
The test fails since we’ve not yet created the loan calculator class. See figure below.
Now, navigate to the app folder and created a new folder named Services. The path should look like app\Services. Inside the services folder create a file named LoanCalculator.php. Add the code below:
Re-run the test. You should see a passing test as shown on the figure below:
Now our application is lacking two more features, and these are ability to issue loans and viewing loans report. Let’s implement these features with their respective tests. Execute the following command to create a loan feature test:
php artisan make:test LoanTest
LoanTest class is created in tests/Feature folder path. First we’re going to test whether a user can view loan issuance form. Open the file and update the code to look like the snippet below:
Run the test by executing the command below.
php artisan test --filter=LoanTest
The test should fail since the route and the form do not exist. Thus let’s implement our code to pass the test. Execute the following command to create a loan controller.
php artisan make:controller LoanController
Open the controller and add the code as shown below:
In views folder, create another folder named loans. The folder path should look like views/loans. In the loans folder create file named issue-loan.blade.php. Open the file and update the code as follows:
Open customers list and add the following td with an issue loan button.
<td>
<a href="{{route('loan.form',$customer->id)}}" class="btn btn-primary" role="button">Issue Loan</a>
</td>
Add the route as follows:
Route::get('/issue-loan/{id}',[LoanController::class, 'index'])->name('loan.form');
Remember to import the LoanController at the top of the route file.
use App\Http\Controllers\LoanController;
Re-run the test. You should see a passing test.
Run the application, navigate to customers list and click the issue loan button. You should see loan issuance form.
Next we’ll add a test to verify that a customer can successfully be issued with a loan. Add the following test method in LoanTest class Ignore the opening PHP tag:
Remember to import LoanCalculator service and Carbon date library at the top of the LoanTest class.
use App\Services\LoanCalculator;
use Carbon\Carbon;
Re-run the test. The second test method should fail because a couple of things are missing. That’s loan model, migration for loans table, route, store method and issue loan form action. Let’s fix these one by one. Update the form action as show below.
<form action="{{route('loan.issue',$customer->id)}}" method="post">
Add the route.
Route::post('/issue-loan',[LoanController::class, 'store'])->name('loan.issue');
Execute the following command to create migration for the loans table:
php artisan make:migration create_loans_table
Update the migration file as shown below:
Create the model by executing the command below:
php artisan make:model Loan
Update the code as below:
Update the loan controller with the following method. Ignore the opening PHP tags.
Remember to import LoanCalculator service, Carbon date library, and Loan model at the top of the LoanController.
use App\Services\LoanCalculator;
use Carbon\Carbon;
use App\Models\Loan;
Also import the Loan model in LoanTest file. Re-run the test. By now you should see two passing tests. Migrate your database to create the loans table and test the application on the browser.
Finally let’s add a test to check whether user can view loan report and implement the code to pass the test. Add the following test method to LoanTest class. Ignore the opening PHP tag.
Re-run the loan test. The test we’ve just added should fail since report page, route and the method to view the report do not exist. Add a button to view report above the table in customers list file.
<a href="/view-report" class="btn btn-success">View Report</a>
Create the report page named loan-report.blade.php in views/loans folder. Add the following code:
Add the route file
Route::get('/view-report',[LoanController::class, 'view_report']);
Re-run the test. Now you should see there passing tests.
You must have noticed we haven’t done any validations on user input. This is to keep the tutorial simple and focus solely on writing tests. You can go an extra mile and validate inputs and also add relationship between customers and loans table.
You can run the test suite (i.e. all the tests) with the command below.
php artisan test
It’s advisable to run the test suite after implementing a new feature to check whether you haven’t broken existing features.
All the tests we’ve created from the beginning must pass as shown on the image below.
By now you should be comfortable with writing tests before writing code to implement application logic. This is what is referred to as Test Driven Development (TDD). In the last part of our series, we’re going to implement authentication in our application so that only logged in users can use it.