PHPUnit Mock Objects и статические методы

Я ищу лучший способ проверить следующий статический метод (в частности, с помощью модели Doctrine):

class Model_User extends Doctrine_Record
{
    public static function create($userData)
    {
        $newUser = new self();
        $newUser->fromArray($userData);
        $newUser->save();
    }
}

В идеале я бы использовал макет-объект, чтобы гарантировать, что "fromArray" (с предоставленными данными пользователя) и "сохранить" были вызваны, но это невозможно, поскольку метод является статическим.

Любые предложения?

Ответ 1

Себастьян Бергманн, автор PHPUnit, недавно опубликовал сообщение в блоге Stubbing and Mocking Static Methods. С PHPUnit 3.5 и PHP 5.3, а также последовательное использование поздней статической привязки вы можете сделать

$class::staticExpects($this->any())
      ->method('helper')
      ->will($this->returnValue('bar'));

Обновление: staticExpects устарело от PHPUnit 3.8 и будет полностью удалено с более поздними версиями.

Ответ 2

В настоящее время библиотека AspectMock помогает:

https://github.com/Codeception/AspectMock

$this->assertEquals('users', UserModel::tableName());   
$userModel = test::double('UserModel', ['tableName' => 'my_users']);
$this->assertEquals('my_users', UserModel::tableName());
$userModel->verifyInvoked('tableName'); 

Ответ 3

Я бы сделал новый класс в пространстве имен unit test, который расширяет Model_User и проверяет это. Вот пример:

Оригинальный класс:

class Model_User extends Doctrine_Record
{
    public static function create($userData)
    {
        $newUser = new self();
        $newUser->fromArray($userData);
        $newUser->save();
    }
}

Класс Mock для вызова unit test (s):

use \Model_User
class Mock_Model_User extends Model_User
{
    /** \PHPUnit\Framework\TestCase */
    public static $test;

    // This class inherits all the original classes functions.
    // However, you can override the methods and use the $test property
    // to perform some assertions.
}

В unit test:

use Module_User;
use PHPUnit\Framework\TestCase;

class Model_UserTest extends TestCase
{
    function testCanInitialize()
    {   
        $userDataFixture = []; // Made an assumption user data would be an array.
        $sut = new Mock_Model_User::create($userDataFixture); // calls the parent ::create method, so the real thing.

        $sut::test = $this; // This is just here to show possibilities.

        $this->assertInstanceOf(Model_User::class, $sut);
    }
}

Ответ 4

Тестирование статических методов обычно считается немного сложным (как вы, вероятно, уже заметили), особенно перед PHP 5.3.

Не могли бы вы изменить свой код, чтобы не использовать статический метод? Я действительно не понимаю, почему вы используете статический метод здесь; возможно, это может быть переписано на некоторый нестатический код, не так ли?


Например, что-то вроде этого не делает трюк:

class Model_User extends Doctrine_Record
{
    public function saveFromArray($userData)
    {
        $this->fromArray($userData);
        $this->save();
    }
}

Не уверен, что вы будете тестировать; но, по крайней мере, никакого статического метода больше...

Ответ 5

Другим возможным подходом является библиотека Moka:

$modelClass = Moka::mockClass('Model_User', [ 
    'fromArray' => null, 
    'save' => null
]);

$modelClass::create('DATA');
$this->assertEquals(['DATA'], $modelClass::$moka->report('fromArray')[0]);
$this->assertEquals(1, sizeof($modelClass::$moka->report('save')));