Files
radicall/docs/TESTING.md
T

14 KiB

Testing Guide

Tests are written only on explicit request. When asked, follow this document exactly and keep the coverage matrix at the bottom up to date so remaining gaps are always visible.

1. What we test (and what we don't)

Business logic lives in app/Services/. Models are mostly relations and $fillable. Testing is prioritised accordingly:

Priority Layer Why
P1 Services Money, auth, scheduling, case flow — the only place real logic lives
P1 Form Requests Cheap, catches every invalid input path before it reaches a service
P1 HTTP (critical routes) Authentication, authorization/ownership, payment callback
P2 Remaining HTTP Doctor & Admin CRUD
P3 Enums Only methods with logic (price(), processingHours(), fromDate(), …)
P3 Models Only scopes, accessors and helper methods — never relations or $fillable

Never test: Eloquent relations, $fillable/$casts, framework behaviour, API Resources on their own (they are asserted through HTTP tests), Vue components (no JS test stack in this project).

2. Directory structure — one source file ↔ one test file

Test files mirror the source path. This is what makes gap detection mechanical.

app/Services/Wallet/WalletService.php          → tests/Feature/Services/Wallet/WalletServiceTest.php
app/Http/Requests/Client/PaymentRequest.php    → tests/Feature/Requests/Client/PaymentRequestTest.php
app/Http/Controllers/Client/PaymentController.php → tests/Feature/Http/Client/PaymentControllerTest.php
app/Enums/ShiftDayEnum.php                     → tests/Unit/Enums/ShiftDayEnumTest.php
app/Models/Shift.php                           → tests/Feature/Models/ShiftTest.php
  • tests/Unit/ — no database, no facades. Enums only.
  • tests/Feature/ — boots the app, uses RefreshDatabase (sqlite :memory:, already configured in phpunit.xml).
  • The default ExampleTest placeholders have been removed; never re-add them.

3. Conventions

  • PHPUnit 11, class-based tests extending Tests\TestCase, RefreshDatabase trait on every Feature test.
  • Method names: test_{action}_{expected}_when_{condition} in snake_case, e.g. test_deposit_increases_balance, test_withdraw_throws_when_balance_is_insufficient.
  • One behaviour per test. Arrange / Act / Assert, separated by a blank line, no comments.
  • Resolve services from the container (app(WalletService::class)), never new.
  • Every model touched by a test must have a factory in database/factories/. Create the missing factory first, in the same style as the existing ones. Factories must produce valid rows with all foreign keys.
  • Assert outcomes, not implementation: assertDatabaseHas, returned values, thrown exceptions, assertInertia props / JSON structure for HTTP.
  • Use enums in assertions (DocumentStatusEnum::Pending), never raw strings.

Fakes — never hit a real external system

Dependency Fake
Zibal (ZibalPaymentService), IranPayamak (IranPayamakService) Http::fake([...]) with Http::assertSent
SendPatternSms job / SmsNotificationService Queue::fake() + Queue::assertPushed(SendPatternSms::class)
FileStorageService, Medium Storage::fake('public'), Storage::fake('private')
Time-dependent logic (shifts, OTP expiry, processing hours) Carbon::setTestNow(...)
Authenticated user $this->actingAs(User::factory()->create())
Spatie roles/permissions seed PermissionSeeder in the test, then $user->givePermissionTo(...) / assignRole(...)

4. Required coverage per layer

Service — happy path; every guard / exception branch; every side effect (DB rows, dispatched jobs, transactions written); for money code, balance invariants before and after.

Form Request — authorize() when it is not return true; each rule with an invalid input → assertSessionHasErrors('field') / 422; one fully valid payload passes.

HTTP — guest is redirected to login; wrong role or non-owner gets 403/404; happy path returns the expected status and persisted state; Inertia page tests assert the component name and key props.

Enum — every method containing logic, including edge values.

Model — each scope filters correctly; each accessor / helper method (isExpired, fullUrl, humanSize, …).

5. Workflow when asked to write tests

  1. Read this file. If the user names a target, test that; otherwise take the next ☐ row by priority (P1 → P3).
  2. Create missing factories first.
  3. Write the test file at the mirrored path (section 2).
  4. Run php artisan test --filter={ClassName} and make it green. Then run the full suite.
  5. Flip the row to ☑ in the matrix below. Add rows for any new Service / Request / Controller / Enum that exists in the code but is missing from the matrix.
  6. Do not modify unrelated tests or source files.

Detecting gaps mechanically

The matrix is the source of truth, but this catches rows that were never added. Anything it lists that is not in the matrix must be added as ☐ or, if it has no logic worth testing, as —:

for f in $(find app/Services app/Http/Requests app/Http/Controllers app/Enums -name '*.php' ! -name 'Controller.php' ! -name 'EnumHasOptions.php'); do t=$(echo "$f" | sed -E 's#^app/Services/#tests/Feature/Services/#; s#^app/Http/Requests/#tests/Feature/Requests/#; s#^app/Http/Controllers/#tests/Feature/Http/#; s#^app/Enums/#tests/Unit/Enums/#; s#\.php$#Test.php#'); [ -f "$t" ] || echo "MISSING $t"; done

6. Coverage matrix

Legend: ☐ not written · ☑ written · — intentionally not tested

Services

Priority Source Test Status
P1 app/Services/Wallet/WalletService.php tests/Feature/Services/Wallet/WalletServiceTest.php ☑
P1 app/Services/Wallet/WithdrawRequestService.php tests/Feature/Services/Wallet/WithdrawRequestServiceTest.php ☑
P1 app/Services/Invoice/InvoiceService.php tests/Feature/Services/Invoice/InvoiceServiceTest.php ☑
P1 app/Services/Payment/ZibalPaymentService.php tests/Feature/Services/Payment/ZibalPaymentServiceTest.php ☑
P1 app/Services/Auth/OtpService.php tests/Feature/Services/Auth/OtpServiceTest.php ☑
P1 app/Services/Auth/AuthService.php tests/Feature/Services/Auth/AuthServiceTest.php ☑
P1 app/Services/Auth/RegistrationService.php tests/Feature/Services/Auth/RegistrationServiceTest.php ☑
P1 app/Services/Shift/ShiftScheduleService.php tests/Feature/Services/Shift/ShiftScheduleServiceTest.php ☑
P1 app/Services/Document/CaseService.php tests/Feature/Services/Document/CaseServiceTest.php ☑
P1 app/Services/Document/FastRequestService.php tests/Feature/Services/Document/FastRequestServiceTest.php ☑
P2 app/Services/Document/DocumentCodeService.php tests/Feature/Services/Document/DocumentCodeServiceTest.php ☑
P2 app/Services/Sms/SmsNotificationService.php tests/Feature/Services/Sms/SmsNotificationServiceTest.php ☑
P2 app/Services/FileStorageService.php tests/Feature/Services/FileStorageServiceTest.php ☑
P3 app/Services/Sms/IranPayamakService.php tests/Feature/Services/Sms/IranPayamakServiceTest.php ☑

Form Requests

Priority Source Test Status
P1 app/Http/Requests/Client/Auth/UserRegistrationRequest.php tests/Feature/Requests/Client/Auth/UserRegistrationRequestTest.php ☐
P1 app/Http/Requests/Client/Auth/VerifyOtpRequest.php tests/Feature/Requests/Client/Auth/VerifyOtpRequestTest.php ☐
P1 app/Http/Requests/Client/PaymentRequest.php tests/Feature/Requests/Client/PaymentRequestTest.php ☐
P1 app/Http/Requests/Client/WalletDepositRequest.php tests/Feature/Requests/Client/WalletDepositRequestTest.php ☐
P1 app/Http/Requests/Client/WithdrawRequestStoreRequest.php tests/Feature/Requests/Client/WithdrawRequestStoreRequestTest.php ☐
P1 app/Http/Requests/Client/BankCardStoreRequest.php tests/Feature/Requests/Client/BankCardStoreRequestTest.php ☐
P1 app/Http/Requests/Client/FastRequestRequest.php tests/Feature/Requests/Client/FastRequestRequestTest.php ☐
P2 app/Http/Requests/Client/CaseRejectRequest.php tests/Feature/Requests/Client/CaseRejectRequestTest.php ☐
P2 app/Http/Requests/Client/ReportSubmitRequest.php tests/Feature/Requests/Client/ReportSubmitRequestTest.php ☐
P2 app/Http/Requests/Client/ProfileUpdateRequest.php tests/Feature/Requests/Client/ProfileUpdateRequestTest.php ☐
P2 app/Http/Requests/Admin/WithdrawRejectRequest.php tests/Feature/Requests/Admin/WithdrawRejectRequestTest.php ☐
P2 app/Http/Requests/Admin/DocumentRequest.php tests/Feature/Requests/Admin/DocumentRequestTest.php ☐
P2 app/Http/Requests/Admin/ShiftRequest.php tests/Feature/Requests/Admin/ShiftRequestTest.php ☐
P2 app/Http/Requests/Admin/UserRequest.php tests/Feature/Requests/Admin/UserRequestTest.php ☐
P2 app/Http/Requests/Admin/PetRequest.php tests/Feature/Requests/Admin/PetRequestTest.php ☐
P2 app/Http/Requests/Admin/ReportRequest.php tests/Feature/Requests/Admin/ReportRequestTest.php ☐
P2 app/Http/Requests/Admin/SettingProfileRequest.php tests/Feature/Requests/Admin/SettingProfileRequestTest.php ☐

HTTP

Priority Source Test Status
P1 app/Http/Controllers/Client/AuthController.php tests/Feature/Http/Client/AuthControllerTest.php ☐
P1 app/Http/Controllers/Client/PaymentController.php tests/Feature/Http/Client/PaymentControllerTest.php ☐
P1 app/Http/Controllers/Client/WalletController.php tests/Feature/Http/Client/WalletControllerTest.php ☐
P1 app/Http/Controllers/Client/FastRequestController.php tests/Feature/Http/Client/FastRequestControllerTest.php ☐
P1 app/Http/Controllers/Client/InvoiceController.php tests/Feature/Http/Client/InvoiceControllerTest.php ☐
P1 app/Http/Controllers/Admin/WithdrawRequestController.php tests/Feature/Http/Admin/WithdrawRequestControllerTest.php ☐
P2 app/Http/Controllers/Client/Doctor/CaseController.php tests/Feature/Http/Client/Doctor/CaseControllerTest.php ☐
P2 app/Http/Controllers/Client/Doctor/ReportController.php tests/Feature/Http/Client/Doctor/ReportControllerTest.php ☐
P2 app/Http/Controllers/Client/Doctor/ShiftController.php tests/Feature/Http/Client/Doctor/ShiftControllerTest.php ☐
P2 app/Http/Controllers/Client/Doctor/DocumentController.php tests/Feature/Http/Client/Doctor/DocumentControllerTest.php ☐
P2 app/Http/Controllers/Client/Doctor/DashboardController.php tests/Feature/Http/Client/Doctor/DashboardControllerTest.php ☐
P2 app/Http/Controllers/Client/MediaController.php tests/Feature/Http/Client/MediaControllerTest.php ☐
P2 app/Http/Controllers/Client/ProfileController.php tests/Feature/Http/Client/ProfileControllerTest.php ☐
P2 app/Http/Controllers/Admin/AuthController.php tests/Feature/Http/Admin/AuthControllerTest.php ☐
P2 app/Http/Controllers/Admin/DocumentController.php tests/Feature/Http/Admin/DocumentControllerTest.php ☐
P2 app/Http/Controllers/Admin/ShiftController.php tests/Feature/Http/Admin/ShiftControllerTest.php ☐
P2 app/Http/Controllers/Admin/UserController.php tests/Feature/Http/Admin/UserControllerTest.php ☐
P2 app/Http/Controllers/Admin/ReportController.php tests/Feature/Http/Admin/ReportControllerTest.php ☐
P2 app/Http/Controllers/Admin/PetController.php tests/Feature/Http/Admin/PetControllerTest.php ☐
P2 app/Http/Controllers/Admin/MediumController.php tests/Feature/Http/Admin/MediumControllerTest.php ☐
P2 app/Http/Controllers/Admin/SettingUserController.php tests/Feature/Http/Admin/SettingUserControllerTest.php ☐
P3 app/Http/Controllers/Admin/Role/RoleController.php tests/Feature/Http/Admin/Role/RoleControllerTest.php ☐
P3 app/Http/Controllers/Admin/Role/PermissionController.php tests/Feature/Http/Admin/Role/PermissionControllerTest.php ☐
P3 app/Http/Controllers/Admin/PanelController.php tests/Feature/Http/Admin/PanelControllerTest.php ☐

Enums

Priority Source Test Status
P3 app/Enums/ShiftDayEnum.php tests/Unit/Enums/ShiftDayEnumTest.php ☐
P3 app/Enums/DocumentModalityEnum.php tests/Unit/Enums/DocumentModalityEnumTest.php ☐
P3 app/Enums/DocumentPriorityEnum.php tests/Unit/Enums/DocumentPriorityEnumTest.php ☐
P3 app/Enums/EnumHasOptions.php (via any enum using it) tests/Unit/Enums/EnumHasOptionsTest.php ☐
— all other enums (values only) —

Models

Priority Source Test Status
P3 app/Models/Shift.php (scopeActive, scopeOpen) tests/Feature/Models/ShiftTest.php ☐
P3 app/Models/Medium.php (fullUrl, humanSize, exists, isPublic, isPrivate, deleteFile) tests/Feature/Models/MediumTest.php ☐
P3 app/Models/OtpCode.php (isExpired) tests/Feature/Models/OtpCodeTest.php ☐
— all other models (relations / fillable only) —

Factories

Existing: User, Pet, Document, DocumentAssignment, Medium, MediaAssignment, Report.

Missing — create when first needed by a test:

Model Status
Wallet ☑
WalletTransaction ☐
WithdrawRequest ☑
BankCard ☑
Invoice ☑
Payment ☑
OtpCode ☑
Shift ☑