2045c856e9
- Add 10 core testing guides (setup, AAA principles, PHPUnit/Pest, mocking, anti-patterns, PHP 8.4+, debugging, data factories, advanced edge cases) - Add 13 component testing guides covering all Laravel layers - Add standard templates for PHPUnit 10/11 and Pest v2/v3 including ArchitectureTest - Add Docker smart test runner, dependency installer, and CI/CD templates (GitHub Actions, GitLab CI) - Add 4 Claude Code skills (laravel-test-setup, laravel-unit-test with --all mode, laravel-test-review, laravel-mock-helper) - Add single unified installer script (sg-install-unit-testing.sh) with global support, CLI runners, and 1-click updater (sg-test-update) - Add comprehensive documentation (README.md, USAGE-GUIDE.md) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
131 lines
3.9 KiB
Markdown
131 lines
3.9 KiB
Markdown
# Component Guide: Middlewares
|
|
|
|
Middleware đứng giữa HTTP Request và ứng dụng, có nhiệm vụ kiểm tra header, xác thực token, kiểm tra role, hoặc biến đổi request trước khi chuyển tiếp cho `$next($request)`.
|
|
|
|
---
|
|
|
|
## 1. Bản Chất Kiểm Thử Middleware
|
|
|
|
Một Middleware có hai kịch bản chính cần Unit Test:
|
|
1. **Pass-through (Cho phép đi tiếp)**: Điều kiện thỏa mãn -> gọi closure `$next($request)` và trả về response từ `$next`.
|
|
2. **Blocked (Chặn lại / Từ chối)**: Điều kiện không thỏa mãn -> trả về `JsonResponse` (401, 403) hoặc `RedirectResponse`, closure `$next` **tuyệt đối không được gọi**.
|
|
|
|
---
|
|
|
|
## 2. Mã Mẫu Chuẩn: Middleware Unit Test
|
|
|
|
### Middleware Nguồn: `App\Http\Middleware\EnsureUserIsAdmin`
|
|
```php
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class EnsureUserIsAdmin
|
|
{
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
$user = $request->user();
|
|
|
|
if (!$user || !$user->is_admin) {
|
|
return response()->json([
|
|
'error' => 'Forbidden. Admin privileges required.'
|
|
], Response::HTTP_FORBIDDEN);
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Test Chuẩn: PHPUnit 10+
|
|
```php
|
|
namespace Tests\Unit\Http\Middleware;
|
|
|
|
use Tests\TestCase;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use App\Http\Middleware\EnsureUserIsAdmin;
|
|
use App\Models\User;
|
|
|
|
class EnsureUserIsAdminTest extends TestCase
|
|
{
|
|
private EnsureUserIsAdmin $middleware;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->middleware = new EnsureUserIsAdmin();
|
|
}
|
|
|
|
#[Test]
|
|
public function it_allows_request_to_proceed_when_user_is_admin(): void
|
|
{
|
|
// Arrange
|
|
$adminUser = new User(['id' => 1, 'is_admin' => true]);
|
|
$request = Request::create('/admin/dashboard', 'GET');
|
|
$request->setUserResolver(fn () => $adminUser);
|
|
|
|
$nextCalled = false;
|
|
$next = function ($req) use (&$nextCalled) {
|
|
$nextCalled = true;
|
|
return response('Next Passed', Response::HTTP_OK);
|
|
};
|
|
|
|
// Act
|
|
$response = $this->middleware->handle($request, $next);
|
|
|
|
// Assert
|
|
$this->assertTrue($nextCalled, 'Closure $next should have been called.');
|
|
$this->assertSame(Response::HTTP_OK, $response->getStatusCode());
|
|
}
|
|
|
|
#[Test]
|
|
public function it_returns_403_forbidden_when_user_is_not_admin(): void
|
|
{
|
|
// Arrange
|
|
$normalUser = new User(['id' => 2, 'is_admin' => false]);
|
|
$request = Request::create('/admin/dashboard', 'GET');
|
|
$request->setUserResolver(fn () => $normalUser);
|
|
|
|
$nextCalled = false;
|
|
$next = function () use (&$nextCalled) {
|
|
$nextCalled = true;
|
|
return response('Next Passed');
|
|
};
|
|
|
|
// Act
|
|
$response = $this->middleware->handle($request, $next);
|
|
|
|
// Assert
|
|
$this->assertFalse($nextCalled, 'Closure $next must NOT be called.');
|
|
$this->assertInstanceOf(JsonResponse::class, $response);
|
|
$this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode());
|
|
}
|
|
|
|
#[Test]
|
|
public function it_returns_403_forbidden_when_no_user_is_authenticated(): void
|
|
{
|
|
// Arrange
|
|
$request = Request::create('/admin/dashboard', 'GET');
|
|
$request->setUserResolver(fn () => null);
|
|
|
|
$nextCalled = false;
|
|
$next = function () use (&$nextCalled) {
|
|
$nextCalled = true;
|
|
return response('Next Passed');
|
|
};
|
|
|
|
// Act
|
|
$response = $this->middleware->handle($request, $next);
|
|
|
|
// Assert
|
|
$this->assertFalse($nextCalled);
|
|
$this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode());
|
|
}
|
|
}
|
|
```
|