Mocking với PHP Unit

Sử dụng Mocking sẽ giúp bạn tạo các test case chuẩn xác hơn cũng như giảm thời gian viết test case, thời gian thực thi test. Vậy mocking là gì?

Mock là việc đóng giả hành vi, thuộc tính của một đối tượng.

Có hai lý do chính để thực hiện việc mock một object:

  1. FileSystem, DbQuery, Cache,...các lớp này phải thao các đến ổ cứng, database hay RAM nên tốc độ sẽ chậm => cần mocking để tăng tốc độ
  2. Khi thực hiện Unit test 1 thành phần ta cần cô lập nó với các thành phần liên quan để có kết quả test chuẩn xác nhất.

Đầu tiên hãy xem xét đoạn code mẫu sau đây

<?php
// File: src/X/HtmlBuilder.php

namespace X;

class HtmlBuilder
{

    public function mailToUser($userId)
    {
        $connection = new \PDO(
            'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
            'your-username',
            'your-password',
            [
                \PDO::ATTR_ERRMODE    => \PDO::ERRMODE_EXCEPTION,
                \PDO::ATTR_PERSISTENT => false
            ]
        );

        $statement = $pdo->query("SELECT username, email FROM users WHERE id = {$userId}");
        $row = $statement->fetch(PDO::FETCH_ASSOC);

        $email = $row['email'];
        $username = $row['username'];

        return "<a href=\"mailto:{$email}?Subject=Hi, {$username}\" target=\"_top\">Get in touch</a>";
    }

}

class \X\HtmlBuilder này chỉ có một method duy nhất làm nhiệm vụ xây dựng mailto link lấy thông tin từ user. Ví dụ chúng ta có email là ‘[email protected]’ và username là ‘Mr. Example’ thì chuỗi trả về sẽ là:

<a href="mailto:[email protected]?Subject=Hi, Mr.Example" target="_top">Get in touch</a>

Ở đây mình cần test: Nếu mình có email là ‘[email protected]’ và username là ‘Mr. Example’ thì khi mình gọi method mailToUser() mình sẽ nhận được chuỗi là

<a href="mailto:[email protected]?Subject=Hi, Mr.Example" target="_top">Get in touch</a>

Nếu viết bình thường không Mocking bạn sẽ phải tạo 1 database test và kết nối vào database đó để thao tác, trong khi cái cần test chỉ là hàm mailToUser(). Nếu những thành phần khác liên quan bị lỗi nhưng thật ra method mailToUser() lại hoạt động thì sao? Kết quả vẫn là test fail mặc dù thành phần bạn cần test hoạt động đúng.

Để thuận tiện hơn cho việc test ta cần sử dụng Dependency Injection như sau

<?php
// File: src/X/HtmlBuilder.php

namespace X;

class HtmlBuilder
{
    /**
     * @var UserInterface
     */
    private $currentUser;

    public function __construct(UserInterface $currentUser)
    {
        $this->currentUser = $currentUser;
    }

    public function mailToUser($userId)
    {
        $email = $this->currentUser->getEmail($userId);
        $username = $this->currentUser->getUsername($userId);

        return "<a href=\"mailto:{$email}?Subject=Hi, {$username}\" target=\"_top\">Get in touch</a>";
    }

}

Giờ để viết test thì bạn hãy sử dụng component này "mockery/mockery": "~0.9"

Khi đó bạn có thể viết test mocking đơn giản như sau:

<?php
// File: tests/HtmlBuilderTest.php

use X\DbUser;

class HtmlBuilderTest extends PHPUnit_Framework_TestCase
{

    public function test_i_want_to_build_mailto_link()
    {
        // prepare
        $demoEmail = '[email protected]';
        $demoUsername = 'Mr. Example';
        $user = $this->getUserMock($demoEmail, $demoUsername);
        $builder = new \X\HtmlBuilder($user);

        // act
        $result = $builder->mailToUser(1);

        // assert
        $this->assertEquals("<a href=\"mailto:{$demoEmail}?Subject=Hi, {$demoUsername}\" target=\"_top\">Get in touch</a>", $result);
    }

    private function getUserMock($demoEmail, $demoUsername)
    {
        $user = Mockery::mock('\X\UserInterface');
        $user->shouldReceive('getEmail')->once()->andReturn($demoEmail);
        $user->shouldReceive('getUsername')->once()->andReturn($demoUsername);

        return $user;
    }
}

Ở đây có thể hiểu đoạn Mocking như sau: $user nên tiếp nhận (shouldReceive) ‘getEmail’ một lần (once) và trả về (andReturn) $demoEmail. Và dĩ nhiên, kết quả khi chạy vendor/bin/phpunit sẽ là pass

Chúc các bạn thành công!


All Rights Reserved