As I am learning more and more about TDD development. I am getting fascinated with this approach of development and the enormous benefits that it brings for the application development.
So I decided to write a tutorial on how to approach TDD with a very simple CRUD application in Laravel.
For those of you unaware of these terms, here is what it means.
TDD Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: first the developer writes an (initially failing) automated test case that defines a desired improvement or new function, then produces the minimum amount of code to pass that.
CRUD The CRUD cycle describes the elemental functions of a persistent database. CRUD stands for Create, Read, Update and Delete. (Retrieve may occasionally be substituted for Read.) These functions are also descriptive of the data life cycle.
So, the TDD approach we are going to follow is very simple.
We are going implement a very basic To-Do application as an example for this tutorial. In this application, you can create, read, update and delete the Tasks.
If you are following along, make sure you have following ready.
Alright, Let’s dig into the steps.
Table of Contents
#1 Setup Model and Migrations
Let’s start thinking here of what our To-Do application will consist of, Since for this example we are considering a very basic To-Do, we can make it very simple with just two Model i.e. Tasks and Users.
And we can think of this relationship that exists between them.
A task is created by users.
A real life application will have many more models but we are going to go with these two for this example.
We already have a User
model available by default in our application at app directory. Let’s start by generating the authentication scaffolding.
This will generate the basic layout file and also code for user authentication (login, logout, registration, forget password etc.)
Next, let’s generate model and controller for our Task model.
This will generate a new Model file named Task.php
in our app directory, also we have appended the command with attributes -mr , this denotes that we also want database migration file to be generated and also a resourceful controller (Controller with default CRUD methods)
Let’s now go ahead and modify the database migration file for our Task database table.
Go to your code editor and navigate to folder database > migrations, you will find a migration file with name create_tasks_table
in this folder. This file was generated as part of make:model
artisan command.
It’s now a good time to think of what data we want to store in our tasks
table. For this simple example. Tasks will have a title and a description and also each and every task is associated with a user.
Modify the up()
method of the migration file like below.
Make sure you have your project connected to database. Run the following artisan command to migrate the database tables.
#2 Generate Model Factory
We are now ready to generate model factory for our database table. Model Factory will be used to seed our table with test data. We will also make use of Model Factory in our TDD tests.
Model Factory are stored at directory database > factories . By default, we already have a model factory for User model with name UserFactory.php
Let’s now generate Model Factory for our Tasks table.
Run the following command on your terminal at project root directory.
We will make use of Faker library to generate dummy content for the title and description of tasks table.
We can now test our model factory. Run php artisan tinker
in your terminal / command-line.
We can seed data into our database using factory method.
This will create 20 rows in your tasks table, and since we have associated user_id to with a new user. This will also create 20 users in the process.
#3 Getting Started with Test Driven Development
Let’s get started with TDD, We will create our tests in folder named tests in the application root directory. This consist of two folders by default Feature
and Unit
. And there is already a test named ExampleTest.php
in both the folders. We can remove this both tests.
Before we start creating a new test.
We don’t want to work with our local database, since that might be already populated with other data. For the tests we should be able to whip up exactly what is needed for our test requirements.
Thus we will use sqlite memory database.
Let’s make some configuration changes to the phpunit.xml file, which is located at the root folder of your laravel application. and set the environment variable as given below
This tells out phpunit to use sqlite and not to use actual database, just do everything in memory.
Sweet!
#4 A user can read all the tasks [READ]
Let’s create our First Test. Go to your tests > Feature directory and create a new file with name TasksTest.php
This is the skeleton of our new Test Class. As you notice that we are importing DatabaseMigration trait in our test. This denotes that for every test we will migrate the database if required and once the test is completed we will roll it back (undo the migrations). Thus for every test we will have a fresh blank database to work with.
Let’s start by adding our first feature test in the Test Class. The first feature we can think of is that, user should be able to read all the tasks in the database. Add a new test that says exactly the same.
That is our first test, we have made use of our factory method to whip-up a new task in the database and we then go to tasks url and assert that we see the task title. Very Simple first test.
Running the test
Now to run this test, go to your application terminal and run the following command.
By using –filter we can run a single Test Class or a single Test method. Of-course the test will fail, since we don’t have any code against the test.
Now, Let’s write our code to make the test form red to green.
Before anything, we need a route file that points to the tasks url. Go to your route file web.php
which is located under routes folder and add a GET route for the tasks url.
Now we’ll modify the index
method of TaskController
to get list of tasks and pass it on to the view.
Here we make use of Task Eloquent model to get all the tasks sorted by created time and pass it on to the view.
Next, as you may have guessed, we need to create a new view file named index.blade.php
under folder resources > tasks .
Alright, we are now all set to run our test again.
There you go, we have now our test giving out green colors, and we have not yet looked at the browser to see if it works. And since now our test is passing, we are sure it’s working in browser too.
#5 A user can read a single task [READ]
Let’s move on to our next feature i.e. a user can read a single task. Which basically means that each and every task has a unique URI path and when we visit that path we can see the tasks details.
Let’s create a new test class a_user_can_read_a_single_task, I have added comments in the method
In this test, we have created a task in our database and we assert that we see the task detail when we visit the single task page. When we run the test this will obviously fail since we don’t have the necessary code in place yet to make it work.
Alright, let’s make the necessary efforts to make this work.
First off , we know that we don’t have necessary route in our web.php
file to make this request work. So, we will create a new GET route in the routes file.
Next, modify the show
method in your TaskController
to get the Task detail by route model binding and then pass on the task to the view.
Now we know that we need to create a new blade file show.blade.php
in our resources > views > tasks folder.
We now have all the required code in place, Run the test again.
And now we get green, Let’s move on to another feature.
#6 An authenticated user can create new task [CREATE]
Let’s move on to the Create part of the CRUD process. Next up , let’s create a test where we assert that an authenticated user can create new task in the database.
This is the skeleton of our new test method, read the comments in the method to understand what we are trying to achieve. Let’s go ahead and write the test itself.
Run the test, and it will give you red color. We don’t have the end-point to create a new task yet.
Let’s dive in to to make this test into green.
First, we need to create an endpoint to create a new task. Add a new route to the web.php route file.
Modify the store method in TaskController
to add logic to add new task into the database.
Run the test and it will still fail, because we haven’t yet gaurded our properties in the Task model. Add this line to your Task model.
Run the test again, and you should get green !
While we are at it, we need to make sure that unauthenticated users should not be able to hit the endpoint to create a new task. Let’s add another test to verify this.
Here we are asserting that when an unathenticated user tries to hit endpoint he should be redirected to the login page. Run the test and it will fail with this error
Caused by
PDOException: SQLSTATE[23000]: Integrity constraint violation: 19 tasks.user_id may not be NULL
This means that unathenticated can still hit the endpoint. We need to fix this.
Go to your TaskController.php file and modify the contruct method to apply auth
middleware.
We have applied auth middleware to all the methods of TaskController except index and show.
Run the test again, and you should get green.
Now that we have the endpoint API ready. That means we can now go ahead and create the front-end ‘form page’ to submit data to this API.
Add a new route to your web.php
routes file.
Go to your TaskController.php
and modify the create method to return the view file to create a new task.
Next up we need to add the view file named create.blade.php
in folder resources > views > tasks
Make sure your create task view files has following.
We already have our endpoint ready and tested. When you submit details from your create task form it should just work.
Testing Validation.
We have our Create New Task form page and the endpoint ready. But we have not handled the validation yet. If you submit your form without entering any data it will post null data to the endpoint and we will get an error from the database regarding the NULL value.
Let’s fix that. We will add a new tests to assert the validation.
Here we have added two new test to verify that we are validating both the fields title and description. Also we specifically passed on a null value to the endpoint and then we are asserting that the session has error with the specified field name in it.
Run the test and it will fail. Since we don’t have validation in place yet.
Let’s add code to validate the form fields. Modify the store method of TaskController
Run the test again and it should pass this time.
We also need to handle this on your front-end to show error messages to the user. Add this code below your form to show error messages
And now we have the validation working.
We are making good progress ! Let’s move on to the next part.
#7 Authorized user can update the task [UPDATE]
Let’s move on to the Update part of our CRUD application. An authorized user should be able to update his tasks.
Add a new test in TasksTest
test class.
Here we have persisted a task in the database and then later we modify the title of the task and make a put request to /task/$task->id url. After that we assert that the title has been updated in the database.
Let’s run the test, and it will fail since we don’t have the API endpoint ready yet to update the task.
Let’s create the update task endpoint. Add the following entry into the web.php
routes file.
We now need to modify the update method of the TaskController class.
Run the test again and it should give you green
Next, we need to make sure that unauthorized user’s should not be able to edit/update the task details. Think about that in a way that you created task but now any authenticated user can edit it. We need to prevent this
Let’s add a new test for this named unauthorized_user_cannot_update_the_task
Here we sign-in as a user and try to hit the update endpoint for a task which is not created by the user. In such case we expect to get UnAuthorized Error or a 403 response status.
Run the test and it will fail, since we don’t have code yet to prevent this.
Let’s fix this
We need to create a Policy which will check if the user is authorized to perform certain actions, Go to your terminal / command-prompt and run the below artisan command
This artisan command will create a new File named TaskPolicy in folder app > Policies
Open TaskPolicy.php
and modify the update method
We have denoted that for update action the tasks user_id should be same as user’s id. Which makes sense, user should only be able to perform edit action on the task that which he owns.
Next, we need to register this policy in AuthServiceProvider.php
which is located in directory app > Providers
Update the policies array variable as below
Next modify the update
method to include authorize code in place
Run the test again and this time it should pass
Let’s move to the front end part of update task. Add a new route to the web.php
routes file
Modify the edit method of TaskController
Add a new view file to show the edit task form named edit.blade.php in resources > views > tasks folder.
We have used {{method_field('PUT'}}
directive in our form to denote that we want to make a PUT request to the action URL.
Next let’s add an edit button to show.blade.php page.
We have added a bootstrap card-footer for the edit button
We are making use of @can blade directive. Which means that the Edit button will only be shown if the user has update authorization.
We are alomst there, Let’s move on to the next and last part of CRUD.
#8 Authorized user can delete the task [DELETE]
Authorized user should also be able to delete their tasks, Let’s add a new test for this named authorized_user_can_delete_the_task
Here, we try to make a delete request to the url and then we assert that the database table tasks no longer have the task.
Run the test and it will give you red.
Let’s make necessary code changes to take the test from red to green.
Add a new entry to route file web.php
Next, modify the destroy method of TaskController to handle the delete request.
Run the test again and it should now pass.
While we are at it, let’s add another test that asserts that unauthorized user should not be able to delete tasks.
This test should pass, since we have already added the authorize method in the destroy method.
Now since we have our tests passing, let’s move to the front-end part of this. Add the following snippet to show.blade.php
This has the form with button to delete the task, put this snippet inside @can directive so that it is only visible to user who are authorized to delete the task.
Run Full Test Suite
We are done with the CRUD implementation in Laravel with TDD. It’s good idea to run the full test suite to check that everything is running as expected and we did not broke anything in our progress.
Run the full suite
Everything should pass
Great ! We wrote 10 tests and made 14 assertions.
Hope you had fun learning the basics of TDD with Laravel and you also realized the benefits it brings to your development process.
You might find related articles useful
This content was originally published here.