如何在单元测试时模拟在 PHP 中静态声明的方法


How to mock methods declared statically in PHP when unit testing

我创建了以下示例测试用例:

<?php
abstract class Model
{
    //...
    public static function factory($data)
    {
        $className = get_called_class();
        $obj = new $className($data);
        return $obj;
    }
}
class User extends Model
{
}
class ExampleController
{
    protected $user;
    public function __construct(User $user)
    {
        $this->user = $user;
    }
    public function create()
    {
        return $this->user->factory(array('name' => 'Jim'));
    }
}
class ExampleTest extends PHPUnit_Framework_TestCase
{
    public function testSomething()
    {
        $user = new User(array('name' => 'Jim'));
        $modelStub = $this->getMockBuilder('User')
            ->disableOriginalConstructor()
            ->getMock();
        $modelStub
            ->method('factory')
            ->with(array('name' => 'Jim'))
            ->willReturn($user);
        $example = new ExampleController($modelStub);
        $this->assertEquals($user, $example->create());
    }
}

但是我收到以下错误:

1) ExampleTest::testSomething
PHPUnit_Framework_MockObject_BadMethodCallException:

当我删除static关键字时,我似乎工作正常,然后我的测试通过了。但我希望我的 Model 类在其他情况下也允许调用某些方法的选项,而无需先实例化:

// when instantiation is required
$userModel = new User();
$user = $userModel->factory(array('name' => 'Jim'));
// called statically, no initial instantiation required
$user = User::factory(array('name' => 'Jim'));

我遇到了这个博客,其中指出静态声明但动态调用的方法可以。但是,动态声明但静态调用的方法将引发 STRICT 错误 - http://www.lornajane.net/posts/2010/declaring-static-methods-in-php

我之前也使用过 Laravel's Eloquent,似乎两种方法调用都可以在那里:

// Eloquent example without initial instantiation is possible too
$user = User::find(1);

无论如何,无论我的代码是否有效,我都希望能够模拟这些静态声明的方法。在阅读时,PHPUnit 似乎不能很好地处理静态方法(我读到有一个 staticExpect 方法,但现在从 PHPUnit 3.8 开始已弃用)。所以我即将开始尝试一些替代的测试框架(Codeception和AspectMock,PHPSpec,mockery),因为我对其他人没有太多经验。非常感谢有关此问题的一些指示或有关此问题的建议,因为它也确实有助于对我们公司的遗留应用程序进行单元测试,谢谢

答案是AspectMock。此库为以下问题提供了答案:

您将如何伪造 time() 函数以为每个测试调用生成相同的结果?有没有办法存根类的静态方法?你能在运行时重新定义类方法吗?

在静态定义方法之前factory不能调用$this->user->factory。您应该将其更改为 User::factory .你可以用 Moka 模拟这样的静态方法:

class ExampleController
{
    private $_userClass;
    public function __construct($userClass = 'User')
    {
        $this->_userClass = $userClass;
    }
    public function create()
    {
        return $this->_userClass::factory(array('name' => 'Jim'));
    }
}
class ExampleControllerTest extends 'PHPUnit_Framework_TestCase
{
    public function testCreateReturnsUser()
    {
        $userClass = Moka::stubClass(null, ['::factory' => 'USER']);
        $controller = new ExampleController($userClass);
        $this->assertEquals('USER', $controller->create());
        $this->assertEquals( 
            [[['name' => 'Jib']], 
            $userClass::$moka->report('::factory')
        );
    }
}