Files
agent-unit-testing/docs/07-troubleshooting-and-debugging.md
Pham-Tan-Thien 2045c856e9 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>
2026-09-07 16:41:37 +07:00

6.0 KiB

07. Troubleshooting & Debugging Guide for Laravel Unit Tests

Cẩm nang chẩn đoán và khắc phục 10 lỗi phổ biến nhất khi viết và chạy Unit Test trong các dự án Laravel.


1. Lỗi: Mockery\Exceptions\InvalidCountException: Method ... was expected to be called 1 times but called 0 times

  • Nguyên nhân:
    • Bạn khai báo ->once() cho một method mock, nhưng luồng code trong hàm thực tế bị rẽ nhánh sớm (early return) hoặc ném exception trước khi chạm tới method đó.
    • Tên method trong shouldReceive('tên_hàm') bị sai chính tả.
  • Cách khắc phục:
    1. Kiểm tra điều kiện if/else trước lệnh gọi: Đảm bảo dữ liệu đầu vào trong // Arrange thỏa mãn để code đi vào nhánh gọi mock.
    2. Kiểm tra log hoặc dùng $this->withoutExceptionHandling() (nếu trong Feature test) để xem có exception nào bị nuốt không.

2. Lỗi: BadMethodCallException: Method Mockery_...::someMethod() does not exist on this mock object

  • Nguyên nhân: Code nguồn gọi phương thức someMethod(), nhưng bạn chưa định nghĩa nó trong mock (shouldReceive('someMethod')).
  • Cách khắc phục:
    • Khai báo bổ sung kỳ vọng cho method đó, hoặc nếu method đó không ảnh hưởng tới kết quả bài test, dùng ->shouldIgnoreMissing() hoặc tạo Spy thay vì Mock:
      $loggerMock = Mockery::spy(LoggerInterface::class);
      

3. Lỗi: Target [App\Contracts\PaymentInterface] is not instantiable while building [App\Services\CheckoutService]

  • Nguyên nhân: Code nguồn cố gắng resolve Interface từ Service Container nhưng Interface đó chưa được bind trong AppServiceProvider trong môi trường test.
  • Cách khắc phục:
    • Trong Unit Test: Khởi tạo trực tiếp bằng từ khóa new và truyền mock vào constructor thay vì gọi app(CheckoutService::class):
      // ĐÚNG CHO UNIT TEST:
      $service = new CheckoutService($mockPayment);
      
      // NẾU CẦN RESOLVE TỪ CONTAINER:
      $this->app->instance(PaymentInterface::class, $mockPayment);
      $service = $this->app->make(CheckoutService::class);
      

4. Lỗi: SQLSTATE[HY000]: General error: 1 no such table: users

  • Nguyên nhân: Test đang chạy trên SQLite :memory: nhưng chưa chạy migration, hoặc trong Unit test bạn vô tình gọi Eloquent Model thật mà không dùng trait RefreshDatabase.
  • Cách khắc phục:
    1. Nếu là Unit Test thuần: Bạn không nên chạm vào DB! Hãy mock Repository hoặc khởi tạo in-memory model $user = new User(['id' => 1]).
    2. Nếu là Repository/Query Test bắt buộc cần DB: Thêm trait use RefreshDatabase; vào đầu class test.

5. Lỗi: SQLSTATE[HY000]: General error: 1 no such function: JSON_CONTAINS / DATE_FORMAT

  • Nguyên nhân: Code nguồn sử dụng hàm SQL đặc thù của MySQL, nhưng môi trường test chạy trên SQLite In-Memory.
  • Cách khắc phục:
    • Đăng ký hàm giả lập trong setUp():
      if (DB::connection() instanceof \Illuminate\Database\SQLiteConnection) {
          DB::connection()->getPdo()->sqliteCreateFunction('JSON_CONTAINS', function ($json, $val) {
              $data = json_decode($json, true) ?? [];
              return in_array(trim($val, '"'), $data) ? 1 : 0;
          }, 2);
      }
      
    • Hoặc chuyển kết nối test trong phpunit.xml sang một database MySQL test độc lập trên Docker.

6. Lỗi: Illuminate\Database\QueryException: Integrity constraint violation: 19 FOREIGN KEY constraint failed

  • Nguyên nhân: SQLite bật kiểm tra khóa ngoại và bản ghi cha không tồn tại.
  • Cách khắc phục:
    • Tạm thời tắt kiểm tra foreign key trong test setup:
      use Illuminate\Support\Facades\Schema;
      
      protected function setUp(): void
      {
          parent::setUp();
          Schema::disableForeignKeyConstraints();
      }
      
    • Hoặc tạo các Model quan hệ bằng Factory: User::factory()->has(Post::factory())->create();.

7. Lỗi: Flaky Test (Chạy riêng lẻ thì Pass, chạy chung cả bộ test thì Fail)

  • Nguyên nhân: Rò rỉ trạng thái (State Leakage) giữa các bài test:
    • Dùng Carbon::setTestNow() nhưng quên reset.
    • Sử dụng static variable hoặc singleton service lưu cache trong memory.
    • Mock Facade tĩnh chưa được giải phóng.
  • Cách khắc phục:
    • Đảm bảo trong tearDown() có gọi:
      protected function tearDown(): void
      {
          Carbon::setTestNow(); // Reset thời gian thực
          Mockery::close();
          parent::tearDown();
      }
      

8. Lỗi: Error: Typed property ... must not be accessed before initialization

  • Nguyên nhân: Thuộc tính khai báo type (ví dụ private UserService $service;) nhưng trong method setUp() bị sót logic gán giá trị, hoặc test method chạy trước khi setUp() hoàn tất.
  • Cách khắc phục: Kiểm tra phương thức setUp() và đảm bảo mọi typed property đều được khởi tạo instance trước khi method test chạy.

9. Lỗi: Class "App\Http\Controllers\Controller" not found

  • Nguyên nhân: Laravel 11 đã loại bỏ base Controller.php mặc định nếu dự án tạo mới tối giản, hoặc namespace bị sai lệch.
  • Cách khắc phục: Kiểm tra class controller đích kế thừa class nào để import chính xác.

10. Lỗi: Session store not set on request

  • Nguyên nhân: Code nguồn gọi request()->session() hoặc $request->session()->get(...) trong Unit test khi chưa có middleware session.
  • Cách khắc phục:
    • Gắn session giả lập vào Request:
      use Illuminate\Session\ArraySessionHandler;
      use Illuminate\Session\Store;
      
      $session = new Store('test_session', new ArraySessionHandler(10));
      $request->setLaravelSession($session);