Laravel: Testing Database Driven applications with PHPUnit Part 2
In part 1 of this tutorial series we created Laravel project, setup test and development databases and defined the project layout with blade templates. In this section we’ll continue with writing tests and code to implement our banking application.
Following the golden rule of TDD, we’ll be writing tests before implementing the code.
Add Customer Test
First let’s create a test to check if a user can view a web form to create a new customer. Run the following command to create the test class.
php artisan make:test CustomerTest
The test will be created under tests/Feature folder path. Open CustomerTest.php and update the generated method to look like the code below:
Run the test by executing the following command.
php artisan test --filter=CustomerTest
The test should fail as we’ve not implemented the route to navigate to the add customer form. See figure below.
Now let’s write some code to pass the test. Run the following command to create a customers controller.
php artisan make:controller CustomersController
Open the customers controller add the following code (Ignore the opening PHP tag):
Under customers folder in views/resources, create a new blade file named create.blade.php.
Update the route file as follows:
Route::get('/create',[CustomersController::class, 'create']);
Remember to import the customers controller at the top the route file. See code snippet below.
use App\Http\Controllers\CustomersController;
Re-run the test. It should pass this time. If you’re still encountering errors check whether you’ve added all files as instructed above. Troubleshoot until the test passes.
Remember we added a form create customer but we didn’t add any code. Thus let’s add one more assertion on our test method to check whether user is looking at the right form. We’ll check for the form title or caption. Update the test code to look like below:
Notice the assertion we’ve added
$response->assertSee('Add Customer');
Re-run the test. The test should fail since we haven’t update our create.blade.php yet.
Next open create.blade.php and add the code below:
Re-run the test. It should pass. Next, navigate to http://localhost:8000/create on your browser. You should see the form similar to the one shown the figure below.
Now let’s create another test to check if customer record can been persisted in the database. In CustomerTest file, if RefreshDatabase and WithFaker traits and TestCase facade were not imported when the test was created you can import them as follows:
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
Use the RefreshDatabase and WithFaker traits in the test class as shown below
class CustomerTest extends TestCase
{
use RefreshDatabase,WithFaker;
Update the customer test class to look like below:
Notice that we’ve used a method named setUp(). This method is where you put the logic that you want to be executed before the test runs. For example for preparing data to be used in test as we’ve already done. We’re creating and authenticating a user just as user would normally login to an application before using it. The user is being created using fake details generated by UserFactory in database/factories folder. We’ll learn how to create factories as we continue writing tests.
Run the test. It should fail as there are so many things we’ve not implemented. First, the error should tell you that we haven’t created the Customer model yet. Also we haven’t created migration for customers table and post route to submit form when adding a new customer. You can be running the test as we fix each of these so that you can learn how to eliminate each error one at a time.
Creating Customers table
To create the customers table we’re not going to use DBMS or MySQL shell. We’ll create a Laravel migration file. In modern web frameworks, migrations are used to create and manage database tables. This has a huge advantage as developers do not need to export and share SQL dump files. Migrations are included as part of the code. Think of them as version control for your database. Run the following command to create customers table:
php artisan make:migration create_customers_table
The file will be created under database/migrations folder path. See Laravel docs to read more information about migrations. Open the migration file. In my case it’s named 2022_07_30_055426_create_customers_table.php. Update column names as below:
Creating Customer Model
Laravel has it’s own ORM called Eloquent. Since we’ll be using Eloquent to interact with the database, we’ll do so using PHP classes called Models. See docs for more information. Now let’s create customer model.
php artisan make:model Customer
The model file will created under app\Models folder path as Customer.php. Open the file and update the code as below:
Notice we’re using the $fillable property on the model to make the attributes of our Customer model mass assignable.
See docs to read more about mass assignment
Add the post route to insert customer into the database.
Route::post('/store',[CustomersController::class, 'store'])->name('customer.store');
Update customers controller with the store method as show below (Ignore the opening PHP tag):
Also remember to import Customer model on top of the customers controller.
use App\Models\Customer;
Run the test. You’ll see two tests have passed as shown on the figure below.
Now update create customer form to submit to the route created above
<form action="{{route('customer.store')}}" method="post">
@csrf
Notice the @csrf token directive. Without it the form won’t submit. See docs for more information about security in Laravel. Now run migration to create customers table.
php artisan migrate
The above command will run all outstanding migrations. That means even the user and other default tables will be created.
Visit http://localhost:8000/create on your browser. Fill in the from and submit. Check your database and a new record must have been inserted.
View Customer Test
Now lets create a test to verify that user can view a list of customers. Add the following test below the setup method (NB: This is not special in any way, it’s just code organisation). Ignore the opening PHP tag.
Update customers controller by adding the following method above the create method (Ignore the opening PHP tag).
Update the route (Remember the index route we created in part 1? we’ll update it to look as below):
Route::get('/', [CustomersController::class, 'index']);
Run the tests. By now you should see three passing tests.
Finalizing Creating and Viewing customers
Update the index.blade.php with the code below.
Update store customer method as follows (Ignore the opening PHP tag):
Notice we’re redirecting to customers list with success or error message depending on whether the customer was saved or not.
Update the index method as shown below. Ignore the opening PHP tag.
Notice we’re are fetching customers records then redirect with the data so that it can be populated on the view. Now do one more manual test on browser by creating a customer record to see our changes in action.
Edit Customer Test
First let’s create a test to check whether user can view edit customer form and customer record can been populated on the form. Update CustomerTest class with the test method below. Ignore the opening PHP tag.
Notice we’re creating a customer record using a factory class as shown below.
//Create a customer record
$this->customer=Customer::factory()->create();
We’ll see how to create factories in a short while.
Run the test. The test should fail since there’s no route, customer factory, method to show the edit customer form and the form itself.
Add route
Route::get('/edit/{id}',[CustomersController::class, 'edit']);
Under resources/views/customers folder path, create a file named edit.blade.php. Add the following code.
Update customers controller with the following method. Ignore the opening PHP tag:
Notice we’re selecting customer record by id and redirecting to edit customer form with the current customer information from database. The customer id comes from url maybe when a user clicks on edit button or link on customers list. For example to select a customer with id of 1 the url will look like this http://localhost:8000/edit/1. If you load the url on the browser you should see information of the customer with id of 1 show on the form. For our test to be able to seed a dummy customer record and simulate showing customer record for editing, we need to create a customer factory. See the docs for more information about seeders and factories.
Creating a Customer Factory
Run the following command to create the customer factory.
php artisan make:factory CustomerFactory
The factory class will be created under database/factories folder path. Open the file and update it as follows:
Run the test. You should see four passing tests.
Let’s now add a button on customers list that user will use navigate to customer edit form. Add the following td on the customers list table.
<td>
<a href="/edit/{{$customer->id}}" class="btn btn-info" role="button">Edit</a>
</td>
Refresh the app on the browser. You should see an edit button appear on each customer record. Click on any of the edit button to see customer record populate on edit customer form.
Let’s now create a test to verify that modified customer record has been updated successfully. Update CustomerTest class with the following test method (Ignore the opening PHP tag):
Remember to declare customer variable in the CustomerTest class. This will store the record of the customer created by factory so as to use the data for assertions.
private $customer;
Run the test. It should fail since we’ve not added route and the method to post the changes made to the customer record. To fix that add the following route.
Route::put('/update/{id}',[CustomersController::class, 'update'])->name('customer.update');
Add update method to customers controller. The code snippet is as follows (Ignore the opening PHP tag):
Update edit.blade.php as follows:
Notice the method field @method(‘PUT’). Remember our update route uses PUT method but the form is making POST request. Thus we have to add this method field for the update action to work. Here’s a detailed explanation from the docs.
Since HTML forms can’t make PUT, PATCH, or DELETE requests, you will need to add a hidden _method field to spoof these HTTP verbs. The @method Blade directive can create this field for you.
Run the test. By now you should see five passing tests.
Finally let’s create a test to check that a customer record can be deleted. Update the CustomerTest class with the following method (Ignore opening PHP tag):
Run the test. It should fail since we’ve not added the route and the method to delete customer record. Update the route file as follows:
Route::get('/customer/{id}',[CustomersController::class, 'delete'])->name('customer.delete');
Add the following td on customers list table on index.blade.php.
<td>
<a href="{{route('customer.delete',$customer->id)}}" class="btn btn-danger" role="button">Delete</a>
</td>
Add the delete method on customers controller
Run the test. So far you’ve six passing tests. See the figure below:
Navigate to customers list on browser and confirm that a customer can be deleted when user clicks in delete button.
On part 3 of this tutorial series we’ll continue writing tests to check if a customer can be issued with a loan, check correct loan calculation and also viewing of loan report.