创建具有默认属性的模拟对象


Creating a mock object with default properties

我需要创建一个具有默认属性集的mock对象,这样它就可以在实例化时在代码库中的其他地方使用。

$mock = $this->getMock('MyClass', array(), array(), 'MyClass_Mock');
$mock->prop = 'foobar';
$myclassMock = new get_class($mock);
var_dump($myclassMock->prop); // NULL
// How can I make this dump 'foobar' ?

我正在测试确定、定位和实例化这些类的框架的一部分,因此注入模拟对象将无法达到测试的目的。

我不需要嘲笑任何方法。。只需动态创建一个模拟类即可:

class MyClass_Mock extends MyClass {
  public $prop = 'foobar';
}

编辑:简化示例

您对使用反射有何感受?

$r = new ReflectionClass('MyClass');
$props = $r->getDefaultProperties();
$mock = new stdClass;
foreach ($props as $prop => $value) {
    $mock->$prop = $value;
}

我自己并没有经常使用"反思",只是为了进行基本的反思。我不确定使用它是否能够完全模拟可见性等,但如果你继续写字符串和evaling.,我不明白为什么不能

编辑:

出于好奇扫描反射函数,如果您在字符串中动态构建类并将其eval,则完全可以使用伪方法完全模拟该类,在适当的情况下实现完全可见性约束、常量和静态元素。

然而,看起来这将是一个完整的任务,以真正完全支持在正确获取数据类型时的所有可能性(例如,您需要代码从数组重建数组构造函数)

祝你好运,如果你走这条路,它需要比我现在愿意腾出的更多的脑力:)

这里有一点代码,您可以对常量做同样的事情,并以类似的方式创建空方法。

class Test
{
    private static $privates = 'priv';
    protected $protected = 'prot';
    public $public = 'pub';
}
$r = new ReflectionClass('Test');
$props = $r->getDefaultProperties();
$mock = 'class MockTest {';
foreach ($props as $prop => $value) {
    $rProp = $r->getProperty($prop);

    if ($rProp->isPrivate()) {
        $mock .= 'private ';
    }
    elseif ($rProp->isProtected()) {
        $mock .= 'protected ';
    }
    elseif ($rProp->isPublic()) {
        $mock .= 'public ';
    }
    if ($rProp->isStatic()) {
        $mock .= 'static ';
    }
    $mock .= "$$prop = ";
    switch (gettype($value)) {
        case "boolean":
        case "integer":
        case "double":
            $mock .= $value;
            break;
        case "string":
            $mock .= "'$value'";
            break;
/*
"array"
"object"
"resource"
*/
    case "NULL":
            $mock .= 'null';
            break;
    }
    $mock .= ';';
}
$mock .= '}';
eval($mock);
var_dump(new MockTest);

我不确定您是否需要来进行测试。

通常,在测试涉及模型访问的代码时,您使用fixture而不是模拟实际模型,因为模型是"愚蠢"的数据结构,不会暴露任何需要模拟的功能。

您的示例证明了这一点:如果您不需要模拟行为(方法),则不需要模拟对象。您需要一个数据固定装置,而模型将其用作数据源。正如您所说,如果"依赖项注入不是一个选项",情况尤其如此。

当然,如果你决定无论如何都要模拟这个模型,我建议@Leigh的反射解决方案。

我昨天刚刚回答了一个关于数据库测试的问题,您可以查看该问题以了解更多详细信息:PHPUnit:如何在远程Postgres服务器上测试数据库交互?

我认为问题在于,您必须让测试中的系统(您的框架)能够使用new直接实例化模型对象,并且每个测试都需要为其属性设置不同的默认值。

如果是这种情况,您可以创建一个简单的基类来在构造时填充一组预定义的属性。下面的解决方案使用了PHP 5.3中的后期静态绑定,但您可以轻松地获得相同的结果,而无需进行轻微的调整。

class MockModel
{
    public static $properties;
    public function __construct() {
        if (isset(static::$properties) && is_array(static::$properties)) {
            foreach (static::$properties as $key => $value) {
                $this->$key = $value;
            }
        }
    }
}
class MockBook extends MockModel { /* nothing needed */ }
function testBookWithTitle() {
    MockBook::$properties = array(
        'title' => 'To Kill a Mockingbird'
    );
    $book = new MockBook;
    self::assertEquals('To Kill a Mockingbird', $book->title);
}

只要您能够向您的框架提供与new一起使用的类名,这就应该有效。如果您需要能够在对框架的一次调用中创建同一模拟类的多个实例,则需要使用某种索引机制来增强以上功能。