PHPUnit模拟对象和静态方法

时间:2010-03-01 15:49:00

标签: php unit-testing mocking doctrine phpunit

我正在寻找测试以下静态方法的最佳方法(特别是使用Doctrine模型):

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

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

有什么建议吗?

8 个答案:

答案 0 :(得分:44)

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

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

更新: staticExpectsdeprecated as of PHPUnit 3.8,将在以后的版本中完全删除。

答案 1 :(得分:11)

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

答案 2 :(得分:1)

我会在单元测试命名空间中创建一个新类,它扩展了Model_User并对其进行测试。这是一个例子:

原班级:

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

要在单元测试中调用的模拟类:

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

答案 3 :(得分:0)

测试静态方法通常被认为是有点难的(正如您可能已经注意到的),尤其是在PHP 5.3之前。

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


例如,像这样的事情可能不起作用:

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

不确定你要测试什么;但是,至少,没有静态方法......

答案 4 :(得分:0)

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

答案 5 :(得分:0)

doublit库也可以帮助您测试静态方法:

/* Create a mock instance of your class */
$double = Doublit::mock_instance(Model_User::class);

/* Test the "create" method */
$double::_method('create')
   ->count(1) // test that the method is called once
   ->args([Constraints::isInstanceOf('array')]) // test that first argument is an array
   ->stub('my_value') // stub the method to return "myvalue"

答案 6 :(得分:0)

approach

class Experiment
{
    public static function getVariant($userId, $experimentName) 
    {
        $experiment = self::loadExperimentJson($experimentName):
        return $userId % 10 > 5;  // some sort of bucketing
    } 

    protected static function loadExperimentJson($experimentName)
    {
        // ... do something
    }
}

在我的ExperimentTest.php

class ExperimentTest extends \Experiment
{
    public static function loadExperimentJson($experimentName) 
    {
        return "{
            "name": "TestExperiment",
            "variants": ["a", "b"],
            ... etc
        }"
    }
}

然后我会像这样使用它:

public function test_Experiment_getVariantForExperiment()
{
    $variant = ExperimentTest::getVariant(123, 'blah');
    $this->assertEquals($variant, 'a');

    $variant = ExperimentTest::getVariant(124, 'blah');
    $this->assertEquals($variant, 'b');
}

答案 7 :(得分:0)

找到了有效的解决方案,尽管主题很旧,但还是愿意分享。 class_alias 可以替换尚未自动加载的类(仅当您使用自动加载时才有效,而不是直接包含/要求文件)。 例如,我们的代码:

class MyClass
{
   public function someAction() {
      StaticHelper::staticAction();
   }
}

我们的测试:

class MyClassTest 
{
   public function __construct() {
      // works only if StaticHelper is not autoloaded yet!
      class_alias(StaticHelperMock::class, StaticHelper::class);
   }

   public function test_some_action() {
      $sut = new MyClass();
      $myClass->someAction();
   }
}

我们的模拟:

class StaticHelperMock
{
   public static function staticAction() {
      // here implement the mock logic, e.g return some pre-defined value, etc 
   }
}

这个简单的解决方案不需要任何特殊的库或扩展。