Testing Route Model Binding in Laravel
In Laravel, route model binding allows you to automatically inject model instances into your routes. This post focuses on how to write tests for routes using model binding, including edge cases like invalid slugs and soft-deleted models.
Step 1: Define the Problem (Testing Route Model Binding)
Imagine you're working on a blog application that fetches posts by their unique slugs instead of IDs. You need to:
- Create a route that uses route model binding to fetch the
Post
model by slug. - Test that the route injects the correct model instance into the controller.
- Handle missing posts gracefully by showing a 404 error page.
Step 2: Setting Up Route Model Binding
To begin, define a route in your routes/web.php
file:
use App\Models\Post;
use Illuminate\Support\Facades\Route;
Route::get('/post/{post:slug}', function (Post $post) {
return view('post.show', compact('post'));
})->name('post.show');
Here, the {post:slug}
syntax binds the Post
model by its slug
column instead of the default id
.
Step 3: Create a Controller for the Route
Now let's create a controller to handle this logic:
php artisan make:controller PostController
Modify the PostController.php
to handle displaying the post:
namespace App\Http\Controllers;
use App\Models\Post;
class PostController extends Controller
{
public function show(Post $post)
{
return view('post.show', compact('post'));
}
}
Step 4: Write Tests for Route Model Binding
Next, write tests to ensure that the route model binding works as expected. Create a test class:
php artisan make:test PostRouteModelBindingTest
Edit the generated test file:
namespace Tests\Feature;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PostRouteModelBindingTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function it_displays_a_post_when_a_valid_slug_is_provided()
{
// Arrange
$post = Post::factory()->create(['slug' => 'my-first-post']);
// Act
$response = $this->get(route('post.show', $post->slug));
// Assert
$response->assertStatus(200);
$response->assertViewIs('post.show');
$response->assertSee($post->title);
}
/** @test */
public function it_returns_404_when_an_invalid_slug_is_provided()
{
// Act
$response = $this->get(route('post.show', 'invalid-slug'));
// Assert
$response->assertStatus(404);
}
}
Step 5: Testing Non-Existent Models
When a model is not found, Laravel throws a ModelNotFoundException
. Customize this behavior to show a custom 404 page:
public function render($request, Throwable $exception)
{
if ($exception instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
return response()->view('errors.404', [], 404);
}
return parent::render($request, $exception);
}
Step 6: Testing Edge Cases
To ensure that slugs with special characters are handled correctly, add the following test:
/** @test */
public function it_handles_special_characters_in_slug()
{
$post = Post::factory()->create(['slug' => 'post-with-special-characters-!@#$']);
$response = $this->get(route('post.show', $post->slug));
$response->assertStatus(200);
$response->assertSee($post->title);
}
Step 7: Handling Soft-Deleted Models
If a post is soft-deleted, Laravel will return a 404 page by default. You can ensure this behavior by adding the following test:
/** @test */
public function it_returns_404_for_soft_deleted_post()
{
$post = Post::factory()->create();
$post->delete();
$response = $this->get(route('post.show', $post->slug));
$response->assertStatus(404);
}
Conclusion
In this post, you’ve learned how to test route model binding in Laravel, handling edge cases like missing models, special characters, and soft-deleted models. These tests ensure your application behaves correctly when using route model binding.
0 Comments