Mockery & Laravel: Đóng Thế Thần Tốc Cho Unit Test
Lavarel

Mockery & Laravel: Đóng Thế Thần Tốc Cho Unit Test

Author

Admin System

@root

Ngày xuất bản

21 Mar, 2026

Lượt xem

3 Lượt

Mockery_PHP

Chào các chiến hữu code! Anh Creyt đây, hôm nay chúng ta sẽ cùng nhau 'mổ xẻ' một công cụ mà anh hay gọi đùa là 'đội ngũ đóng thế chuyên nghiệp' trong thế giới lập trình: Mockery PHP. Đặc biệt là cách nó 'song kiếm hợp bích' với Laravel để biến những bài kiểm thử (unit test) của chúng ta trở nên mượt mà, nhanh chóng và đáng tin cậy hơn bao giờ hết.

1. Mockery là gì và tại sao chúng ta cần nó?

Tưởng tượng thế này nhé: Em đang đạo diễn một bộ phim hành động gay cấn. Diễn viên chính của em là một 'chàng Service' dũng cảm, nhiệm vụ của anh ta là 'cứu thế giới' bằng cách gọi điện cho 'chị Repository' để lấy thông tin, rồi 'gửi tin nhắn' cho 'anh EmailService' để thông báo kết quả. Giờ em muốn quay một cảnh cận cảnh 'chàng Service' hành động, nhưng em không muốn mỗi lần quay lại phải gọi điện thật, gửi tin nhắn thật, vì làm thế vừa tốn thời gian, tốn tiền mà lại còn có thể gây ra những hậu quả không mong muốn (gửi nhầm email cho khách hàng chẳng hạn!).

Đó chính là lúc Mockery bước vào sân khấu! Mockery chính là những 'diễn viên đóng thế' chuyên nghiệp cho 'chị Repository', 'anh EmailService' hay bất kỳ 'nhân vật' phụ nào mà 'chàng Service' của em cần tương tác. Thay vì dùng 'nhân vật' thật (mà có thể liên quan đến database, API ngoài, hệ thống file...), Mockery cho phép em tạo ra những 'bản sao giả' (mock objects) có hành vi được định nghĩa trước. Em bảo nó 'khi gọi phương thức X thì trả về Y', và nó sẽ làm đúng như vậy, không hơn không kém.

Mục đích chính của Mockery là giúp chúng ta:

  • Cô lập code: Khi test một đơn vị code (ví dụ: một phương thức trong class), chúng ta chỉ muốn test đúng code đó, không muốn các yếu tố bên ngoài (như database, API) ảnh hưởng hay làm chậm quá trình.
  • Kiểm soát hành vi: Dễ dàng giả lập các kịch bản khác nhau (thành công, thất bại, trả về dữ liệu rỗng...) mà không cần thay đổi môi trường thật.
  • Tăng tốc độ test: Tránh các thao tác I/O chậm chạp như truy vấn database hay gọi API.
Illustration

2. Code Ví Dụ Minh Hoạ: Mockery trong Laravel

Giả sử em có một UserService trong Laravel, có nhiệm vụ lấy thông tin người dùng từ UserRepository và có thể thực hiện một số logic nghiệp vụ. Chúng ta sẽ dùng Mockery để test UserService mà không cần đụng đến database thật.

Đầu tiên, hãy tạo một interface và một service đơn giản:

// app/Contracts/UserRepositoryInterface.php
namespace App\Contracts;

interface UserRepositoryInterface
{
    public function find(int $id): ?array;
    public function create(array $data): array;
}
// app/Services/UserService.php
namespace App\Services;

use App\Contracts\UserRepositoryInterface;

class UserService
{
    protected $userRepository;

    public function __construct(UserRepositoryInterface $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function getUserProfile(int $userId): ?array
    {
        $user = $this->userRepository->find($userId);

        if ($user) {
            // Giả sử có thêm logic xử lý profile ở đây
            $user['full_name'] = $user['first_name'] . ' ' . $user['last_name'];
        }

        return $user;
    }
}

Bây giờ, chúng ta sẽ viết một bài test cho UserService sử dụng Mockery. Laravel đã tích hợp sẵn PHPUnit, và Mockery hoạt động rất mượt mà với nó. Em chỉ cần chạy php artisan make:test UserServiceTest --unit để tạo file test.

// tests/Unit/UserServiceTest.php
namespace Tests\Unit;

use Tests\TestCase;
use App\Services\UserService;
use App\Contracts\UserRepositoryInterface;
use Mockery; // Quan trọng: import Mockery

class UserServiceTest extends TestCase
{
    protected function tearDown(): void
    {
        // Đảm bảo các mock object được dọn dẹp sau mỗi test
        // Laravel và PHPUnit thường tự động xử lý, nhưng đây là một thói quen tốt
        Mockery::close();
        parent::tearDown();
    }

    /** @test */
    public function it_can_get_a_user_profile_successfully()
    {
        // 1. Tạo một mock object cho UserRepositoryInterface
        // Đây chính là 'diễn viên đóng thế' của chúng ta
        $mockUserRepository = Mockery::mock(UserRepositoryInterface::class);

        // 2. Định nghĩa hành vi của mock object
        // "Khi phương thức 'find' được gọi với tham số 1, hãy trả về dữ liệu này"
        $mockUserRepository->shouldReceive('find')
                           ->once() // Đảm bảo phương thức này chỉ được gọi đúng 1 lần
                           ->with(1) // Đảm bảo nó được gọi với tham số là 1
                           ->andReturn([ // Và trả về dữ liệu giả này
                               'id' => 1,
                               'first_name' => 'John',
                               'last_name' => 'Doe',
                               'email' => 'john.doe@example.com'
                           ]);

        // 3. Khởi tạo UserService với mock object
        // Bây giờ UserService sẽ làm việc với 'diễn viên đóng thế' chứ không phải repo thật
        $userService = new UserService($mockUserRepository);

        // 4. Gọi phương thức cần test
        $userProfile = $userService->getUserProfile(1);

        // 5. Kiểm tra kết quả
        $this->assertNotNull($userProfile);
        $this->assertEquals('John Doe', $userProfile['full_name']);
        $this->assertEquals('john.doe@example.com', $userProfile['email']);
    }

    /** @test */
    public function it_returns_null_if_user_not_found()
    {
        $mockUserRepository = Mockery::mock(UserRepositoryInterface::class);

        // Giả lập trường hợp không tìm thấy người dùng
        $mockUserRepository->shouldReceive('find')
                           ->once()
                           ->with(999) // ID không tồn tại
                           ->andReturn(null); // Trả về null

        $userService = new UserService($mockUserRepository);
        $userProfile = $userService->getUserProfile(999);

        $this->assertNull($userProfile);
    }
}

Thấy chưa? Với Mockery, chúng ta đã test được logic của UserService mà không cần chạy bất kỳ câu lệnh SQL nào. Nhanh gọn, chính xác và hoàn toàn độc lập!

3. Mẹo (Best Practices) từ 'Giảng viên Lão luyện'

Để sử dụng Mockery hiệu quả như một 'tay chơi' thực thụ, hãy nhớ mấy 'mẹo' sau đây:

  • Mock Interfaces, not Concrete Classes (khi có thể): Nếu em có một interface, hãy mock interface đó. Điều này giúp code của em linh hoạt hơn và dễ thay đổi trong tương lai. Nếu không có interface, mock class cũng được, nhưng cân nhắc việc tạo interface nếu thấy cần thiết cho testability.
  • Don't Mock Value Objects: Đừng bao giờ mock những đối tượng chỉ chứa dữ liệu đơn giản (ví dụ: User object chỉ có id, name, email). Chúng không có hành vi phức tạp để cần đóng thế. Hãy dùng đối tượng thật hoặc tạo dữ liệu giả trực tiếp.
  • Mock Collaborators, not the Class Under Test: Em mock các phụ thuộc (collaborators) của class mà em đang test, chứ không phải chính class đó. Mục tiêu là kiểm tra xem class của em tương tác đúng với các phụ thuộc của nó hay không.
  • Be Specific with Expectations: Đừng 'bắt' mock object phải làm quá nhiều thứ không cần thiết. Chỉ định rõ ràng phương thức nào sẽ được gọi, bao nhiêu lần, với tham số nào và trả về gì. Điều này giúp test dễ đọc và dễ bảo trì hơn. Ví dụ: ->once(), ->times(2), ->withAnyArgs(), ->with(1, 'foo').
  • Clean Up Your Mocks: Mặc dù Laravel/PHPUnit thường tự động gọi Mockery::close() trong tearDown() của test case, nhưng việc gọi nó tường minh hoặc hiểu rằng nó đang diễn ra là quan trọng. Nó giúp dọn dẹp các mock object, tránh xung đột giữa các test case.
  • When to Use Spies vs. Mocks: Đôi khi em cần một Spy (gián điệp) thay vì Mock. Mock định nghĩa hành vi trước khi gọi code. Spy định nghĩa hành vi sau khi gọi code và muốn kiểm tra xem một phương thức có được gọi hay không. Nhưng đó là câu chuyện khác, tạm thời cứ nắm chắc Mock đã!

4. Ứng dụng Thực tế: Mockery 'làm mưa làm gió' ở đâu?

Mockery không phải là 'đồ chơi' riêng của các dự án nhỏ đâu nhé. Nó là một công cụ sống còn trong các dự án lớn, phức tạp, nơi mà việc đảm bảo chất lượng code là ưu tiên hàng đầu.

  • Hệ thống E-commerce (Thương mại điện tử): Em có thể test logic xử lý giỏ hàng, đặt hàng, tính toán giá mà không cần thực sự kết nối với cổng thanh toán (Stripe, PayPal) hay hệ thống quản lý kho. Mock PaymentGateway, Mock InventoryService.
  • CRM (Quản lý quan hệ khách hàng): Khi test các tính năng gửi email tự động, tạo task, cập nhật trạng thái khách hàng, em có thể mock EmailService, TaskService, tránh gửi email thật hay tạo task ảo trong hệ thống production hoặc staging.
  • SaaS Applications (Phần mềm dịch vụ): Các ứng dụng này thường tích hợp với rất nhiều API bên ngoài (Slack, Google Drive, AWS S3...). Mockery giúp em test các tương tác này mà không cần gọi API thật, tránh phát sinh chi phí hoặc giới hạn rate limit.
  • Microservices Architectures: Khi các service giao tiếp với nhau qua API, Mockery là cứu cánh để test một service độc lập mà không cần phải chạy tất cả các service khác.

Tóm lại, Mockery không chỉ là một công cụ, nó là một tư duy. Tư duy về việc cô lập, kiểm soát và tăng tốc độ kiểm thử. Nắm vững nó, em sẽ là một 'đạo diễn' tài ba, có thể dựng lên những cảnh phim (test case) hoàn hảo nhất cho 'siêu phẩm' code của mình!

Thuộc Series: Lavarel

Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!