PHPUnit模拟对象和静态方法

我正在寻找最好的方法去testing下面的静态方法(特别是使用Doctrine模型):

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

理想情况下,我会使用一个模拟对象来确保“fromArray”(使用提供的用户数据)和“save”被调用,但这是不可能的,因为方法是静态的。

有什么build议么?

PHPUnit的作者Sebastian Bergmann最近发表了一篇关于Stubbing和Mocking Static Methods的博文。 随着PHPUnit 3.5和PHP 5.3以及一致的使用后期静态绑定,你可以做

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

更新: 从PHPUnit 3.8开始 , staticExpects已被弃用,并且将会被更高版本完全删除。

现在有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'); 

通常认为testing静态方法有点困难(你可能已经注意到了) ,特别是在PHP 5.3之前。

你可以不修改你的代码不使用静态方法? 事实上,我不明白你为什么在这里使用静态方法。 这可能会重新写入一些非静态的代码,不是吗?

例如,这样的事情可能不是诀窍:

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

不知道你要testing什么 但是,至less,没有静态的方法了…

另一种可能的方法是使用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'))); 

我将在扩展Model_User并testing它的unit testing命名空间中创build一个新类。 这是一个例子:

原始类:

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

模拟类调用unit testing:

 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 testing中:

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