Foundation · 45 MINUTES · VERSION 1
Laravel routing, validation, and a feature test
Validate a protected task-preview form, select accepted fields and test HTML/JSON failures.
Reviewed 23 Oct 2026Your setup may differ. Windows, Ubuntu, and optional tool bundles →
What you will learn
- Register GET and POST routes for a task preview.
- Validate input with Laravel and select accepted fields.
- Test form and JSON success/failure without storing a task.
Theory: request, validation, response
A route connects a method/path to an action. Request validation checks an input contract. This preview accepts a required text title up to 80 characters and a fixed status. It returns selected fields, not every submitted value. Validation does not authenticate a person or authorize a database change. There is no database write in this exercise.
Laravel normally redirects a failed HTML form back with session errors. A request expecting JSON gets validation errors with status 422. The successful JSON preview returns 200 and saved=false; it does not claim creation. GET displays the form. POST handles the proposed input. The form includes a CSRF field; keep web middleware and protection enabled.
Prepare a separate practice project
Use an existing disposable Laravel 13 project with dependencies installed and PHP compatible with its composer.json. Check php artisan --version and php --version. Windows and Ubuntu use the same PHP/source commands. Laragon/XAMPP are optional. Do not paste this into company routes or replace this learning site's files. If starting fresh, follow the official Laravel installation guide, choose a new empty directory, and record versions. Configure a writable local session store, such as file sessions; never point practice at this site's database. This lab needs no database or paid service.
Worked example: route and view
In your practice project's routes/web.php, keep the existing opening PHP tag and imports, then add these imports/routes without duplicating imports. This full snippet shows a standalone routes file; merge it carefully if your practice file already has content:
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Validation\Rule;
Route::get('/study/task-preview', fn () => view('study-task-preview'))->name('study.preview');
Route::post('/study/task-preview', function (Request $request) {
$validated = $request->validate([
'title' => ['required', 'string', 'max:80'],
'status' => ['required', Rule::in(['todo', 'in_progress', 'done'])],
]);
$preview = ['title' => trim($validated['title']), 'status' => $validated['status'], 'saved' => false];
if ($request->expectsJson()) {
return response()->json($preview);
}
return redirect()->route('study.preview')->with('preview', $preview);
});
Create resources/views/study-task-preview.blade.php:
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Task preview</title></head>
<body>
<h1>Task preview — no task is stored</h1>
@if($errors->any())<ul>@foreach($errors->all() as $error)<li>{{ $error }}</li>@endforeach</ul>@endif
<form method="POST" action="{{ route('study.preview') }}">
@csrf
<label>Title <input name="title" value="{{ old('title') }}" required maxlength="80"></label>
<label>Status <select name="status"><option value="todo">todo</option><option value="in_progress">in_progress</option><option value="done">done</option></select></label>
<button>Validate preview</button>
</form>
@if(session('preview'))<p>Valid preview: {{ session('preview.title') }} · {{ session('preview.status') }}. Nothing was saved.</p>@endif
</body>
</html>
Laravel's normal string-trimming middleware makes whitespace-only titles fail required validation. The explicit trim also makes the displayed title clear. If middleware is customized, define and test normalization deliberately. This max rule measures string characters; the earlier CLI byte limit was a different contract. Blade's escaped output helps keep a proposed HTML-looking title as text.
Start only your practice project's server on an unused local port, for example php artisan serve --host=127.0.0.1 --port=8011. Open /study/task-preview there. Submit Read/todo: expect a redirect and “Valid preview,” with nothing stored. Submit a blank title: expect a field error. Browser required fields may stop submission first; the server tests below verify the server rule independently. Keep the learning site's existing port 8003 process running.
Guided lab: real framework feature tests
Create tests/Feature/StudyTaskPreviewTest.php in that practice project:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class StudyTaskPreviewTest extends TestCase
{
public function test_form_contains_a_csrf_field_and_valid_input_is_only_previewed(): void
{
$this->get('/study/task-preview')->assertOk()->assertSee('name="_token"', false);
$this->post('/study/task-preview', ['title' => ' Read ', 'status' => 'todo', 'is_admin' => true])
->assertRedirect('/study/task-preview')->assertSessionHas('preview', ['title' => 'Read', 'status' => 'todo', 'saved' => false]);
}
public function test_json_success_and_invalid_input_use_framework_validation(): void
{
$this->postJson('/study/task-preview', ['title' => 'Read', 'status' => 'done', 'is_admin' => true])
->assertOk()->assertExactJson(['title' => 'Read', 'status' => 'done', 'saved' => false]);
$this->postJson('/study/task-preview', ['title' => '', 'status' => 'blocked'])
->assertUnprocessable()->assertJsonValidationErrors(['title', 'status']);
$this->postJson('/study/task-preview', ['title' => ['Read'], 'status' => 'todo'])
->assertUnprocessable()->assertJsonValidationErrors('title');
$this->from('/study/task-preview')->post('/study/task-preview', ['title' => '', 'status' => 'todo'])
->assertRedirect('/study/task-preview')->assertSessionHasErrors('title');
}
}
Run php artisan test --filter=StudyTaskPreviewTest. Expect two tests to pass, not a particular duration or framework-wide count. The tests check the session preview, selected JSON fields, rejected array titles, invalid statuses and form errors. Test requests run through the application in memory; they do not need a listening server. Standard Laravel HTTP tests disable CSRF checks in tests, so passing them does not prove real browser CSRF behavior. Keep the token in the actual form and verify that form separately. These tests use no database reset or migration.
Independent exercise
Add a required priority value limited to low or high. Add it to both form and validation, select it into the preview, and extend the success and failure tests. Try missing priority and urgent. Keep saved=false. Predict the errors before running tests. Do not accept is_admin or pretend the preview stored a task. Check an 80-character title and an 81-character title as boundaries.
Common mistakes
- Returning all input after validating a few fields: return selected validated fields.
- Removing CSRF to fix a form: verify middleware, session, origin and token instead.
- Checking only valid inputs: test invalid field types and rules too.
- Treating a passing HTTP test as a device/browser test.
- Running a practice test against a company database.
Self-check — not graded
- What does an invalid JSON-expecting request normally receive here?
- Is is_admin included in the preview?
- Does this POST create a task?
Interview practice
“What should one feature test prove?” Describe a concrete request, response/status and important side effects. Cover an invalid path through the rules and a valid result. Use database assertions when storage is actually part of the feature; this preview has none.
English practice and glossary
- Route: a method/path mapping.
- Validation: input-rule checking.
- Feature test: a test of application behavior through a boundary.
- Session: state associated with a browser session.
- CSRF: protection against unwanted cross-site requests using a user's browser session.
Write: “The request contains __. The server rejects __. I verified this with __.” Explain what your test does not prove.
Recap and next step
Use framework validation, explicit responses, protected forms and focused failure tests. Next, explain a bug and fix clearly in English. Sources below were checked on 2026-10-22; actual browser/device review remains pending.
Reveal exercise hints and self-check answers
- A 422 JSON validation response under these routes and normal middleware.
- No. The preview explicitly selects title and status, plus the fixed saved flag.
- No. The response is a validation preview, not task storage.
Exercise outline: add a priority select with low/high choices, a required allowed-value rule, and an explicit preview field. Update exact expected JSON/session payloads. Add missing/invalid priority cases expecting field errors. Re-run prior cases, title boundaries, and the real browser form. Permission and persistence are later separate features.