feat: complete Smartgift AI Laravel unit testing toolkit

- 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>
This commit is contained in:
2026-09-07 16:41:37 +07:00
commit 2045c856e9
60 changed files with 5248 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
use {{TargetClassFullName}};
beforeEach(function () {
// $this->action = new {{ClassName}}(...);
});
describe('{{ClassName}}', function () {
it('handles action invocation successfully', function () {
// Arrange
// Act
// $result = ($this->action)($dto);
// Assert
expect(true)->toBeTrue();
});
});
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
/*
|--------------------------------------------------------------------------
| Pest PHP Architecture Test Suite (Enterprise Pro Level)
|--------------------------------------------------------------------------
|
| Bộ kiểm thử kiến trúc toàn diện bảo vệ cấu trúc mã nguồn không bị suy thoái.
| Chạy bằng lệnh: ./test-runner.sh tests/Unit/ArchitectureTest.php
|
*/
describe('Architecture & Code Quality Gates', function () {
test('All files must use strict types')
->expect('App')
->toUseStrictTypes();
test('No debugging functions left behind in codebase')
->expect(['dd', 'dump', 'ray', 'var_dump', 'print_r', 'die', 'exit'])
->not->toBeUsed();
test('Controllers naming convention & inheritance')
->expect('App\Http\Controllers')
->toHaveSuffix('Controller');
test('Controllers should not directly query Eloquent Models')
->expect('App\Http\Controllers')
->not->toUse('App\Models')
->ignoring('App\Http\Controllers\Auth');
test('Services must be final or readonly')
->expect('App\Services')
->toHaveSuffix('Service');
test('DTOs and Value Objects must be immutable (readonly)')
->expect('App\DTOs')
->toBeReadonly();
test('Custom Validation Rules must implement ValidationRule contract')
->expect('App\Rules')
->toImplement('Illuminate\Contracts\Validation\ValidationRule');
test('Jobs must be queueable')
->expect('App\Jobs')
->toImplement('Illuminate\Contracts\Queue\ShouldQueue');
test('Events must not have dependencies injected')
->expect('App\Events')
->classes()
->not->toHaveConstructor();
});
+12
View File
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
use Illuminate\Console\Command;
describe('{{CommandSignature}} Command', function () {
it('executes artisan command successfully', function () {
$this->artisan('{{CommandSignature}}')
->assertExitCode(Command::SUCCESS);
});
});
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
use {{TargetClassFullName}};
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
beforeEach(function () {
// $this->controller = new {{ClassName}}(...);
});
describe('{{ClassName}}', function () {
it('returns successful response', function () {
// Arrange
// $request = Request::create('/api/resource', 'GET');
// Act
// $response = $this->controller->index($request);
// Assert
// expect($response->getStatusCode())->toBe(Response::HTTP_OK);
expect(true)->toBeTrue();
});
});
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
use {{TargetClassFullName}};
use Illuminate\Support\Facades\Validator;
describe('{{ClassName}}', function () {
it('authorizes request appropriately', function () {
$request = new {{ClassName}}();
expect($request->authorize())->toBeTrue();
});
it('passes validation with valid payload', function () {
$request = new {{ClassName}}();
$validator = Validator::make([
// Valid data here
], $request->rules());
expect($validator->fails())->toBeFalse();
});
it('fails validation when required fields are missing', function () {
$request = new {{ClassName}}();
$validator = Validator::make([], $request->rules());
expect($validator->fails())->toBeTrue();
});
});
+19
View File
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
use {{TargetClassFullName}};
describe('{{ClassName}}', function () {
it('handles job execution successfully', function () {
// Arrange
// $dependencyMock = Mockery::mock(Service::class);
// $job = new {{ClassName}}(...);
// Act
// $job->handle($dependencyMock);
// Assert
expect(true)->toBeTrue();
});
});
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
use {{TargetClassFullName}};
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
beforeEach(function () {
$this->middleware = new {{ClassName}}();
});
describe('{{ClassName}}', function () {
it('passes request to next closure when authorized', function () {
$request = Request::create('/test-route', 'GET');
$nextCalled = false;
$next = function ($req) use (&$nextCalled) {
$nextCalled = true;
return response('OK', Response::HTTP_OK);
};
$response = $this->middleware->handle($request, $next);
expect($nextCalled)->toBeTrue();
expect($response->getStatusCode())->toBe(Response::HTTP_OK);
});
});
+24
View File
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
use {{TargetClassFullName}};
use App\Models\User;
beforeEach(function () {
$this->policy = new {{ClassName}}();
});
describe('{{ClassName}}', function () {
it('allows authorized user to perform action', function () {
$user = new User(['id' => 1]);
// expect($this->policy->view($user, $model))->toBeTrue();
expect(true)->toBeTrue();
});
it('denies unauthorized user from performing action', function () {
$user = new User(['id' => 99]);
// expect($this->policy->update($user, $model))->toBeFalse();
expect(true)->toBeTrue();
});
});
+25
View File
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
use {{TargetClassFullName}};
beforeEach(function () {
// $this->dependencyMock = Mockery::mock(DependencyInterface::class);
// $this->service = new {{ClassName}}($this->dependencyMock);
});
describe('{{ClassName}}', function () {
it('executes successfully under normal conditions', function () {
// Arrange
// Act
// Assert
expect(true)->toBeTrue();
});
it('throws exception when input is invalid', function () {
// expect(fn () => $this->service->method(...))->toThrow(\InvalidArgumentException::class);
});
});