Back to Course |
Testing in Laravel 11 For Beginners

TDD Approach: Simple Example

Until now, we have been writing tests on top of existing Laravel code. We haven't touched a different (opposite) approach called TDD, Test Driven Development. So let's briefly cover it in this final section of the course.


The TDD Approach

In general, the difference is in mindset and in thinking, and in the order of how you do things.

Until now, we have been working in this order:

  • Write the feature code
  • Write the test for features
  • Launch the tests
  • If they fail, fix the feature code and relaunch tests

TDD approach is the opposite:

  • You write tests first
  • Then they fail (intentionally)
  • And then you write the code for features, fixing the test errors
  • Launch the tests again
  • They fail with different errors
  • You write more code for features
  • ... Repeat until the tests succeed

For the example, we will write a couple of the same tests we did already for a product CRUD, but with a TDD approach.


The Practical Example

So, we have a fresh Laravel project. The first feature we will work on is a products list, which should be protected by authentication. Let's create a test.

php artisan make:test ProductsTest --phpunit

tests/Feature/ProductsTest.php:

class ProductsTest extends TestCase
{
public function test_unauthenticated_user_cannot_access_products_page()
{
$response = $this->get('/products');
 
$response->assertRedirect('login');
}
}

Notice: for TDD examples, I will use the PHPUnit syntax and not Pest, because it seems that TDD+PHPUnit are popular specifically in that combination, as both are widely used outside of Laravel framework, in other PHP projects.

Then, we launch the test without writing any code which will fail.

And the failure of the test is the whole point. The error of that failure would be our next step in what feature to write.

Here, we got a 404 error code, which means we need to create a route. For the Route we need to pass the Controller, so we must create it first.

php artisan make:controller ProductController --resource

routes/web.php:

use App\Http\Controllers\ProductController;
 
Route::get('/', function () {
return view('welcome');
});
 
Route::get('products', [ProductController::class, 'index'])->name('product.index');

The index method in the ProductController exists, but it doesn't contain any code. Our goal is to make the first test passed. Now, let's re-launch the test to see what error we have and lead to what we create next.

We received a status of 200, but we expect it to be a redirect to login. Now, we need to create a login Route.

Then you think you will build an authentication system using, for example Laravel Breeze.

composer require laravel/breeze --dev
php artisan breeze:install blade

Now, if we run tests, there will be more passed tests that came from the Breeze. So, we can filter tests just for class.

We have the same 404 message. And this is because Laravel Breeze automatically changed the routes file during the installation. That's why you should use a Starter Kit first, before writing any code. Now we must add Route again. But now, we can add this Route in the auth Middleware group.

route/web.php:

use App\Http\Controllers\ProductController;
 
Route::get('/', function () {
return view('welcome');
});
 
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
 
Route::middleware('auth')->group(function () {
Route::get('products', [ProductController::class, 'index'])->name('product.index');
 
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
 
require __DIR__.'/auth.php';

Now, after running the test, it is green.

This was our first goal with TDD. And another point is that nowhere during this lesson did we need to use the browser to test something. You write the test like you would do that in the browser, simulating the scenario.