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

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

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

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

Какие-либо предложения?

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

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

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

В настоящее время библиотека 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'); 

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

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

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

Mock Class для вызова в модульном тесте:

 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. } 

В вашем модульном тесте:

 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); } } 

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

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

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

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

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

Другой возможный подход – с библиотекой 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')));