PHPunit mock只能观察

3

我在理解模拟对象方面遇到了一些问题。

我的需求是一个正常工作的观察者,但需要确保方法被使用正确的参数调用。

根据目前的理解,这应该是我要寻找的内容:

观察者:

class Observer
{
    public function returnFooIfBar($bar)
    {
        return ($bar == 'bar') ? 'foo' : ;
    }
}

主题:

class Subject
{
    $obs;
    __construct(Observer $dependency)
    {
        $this->obs = $dependency;
    }

    public function tradeStrings($string)
    {
        $this->obs->returnFooIfBar($string);
    }
}

测试:

class SubjectTest
{
    public function testCallsObsMethod()
    {
        $obs = $this->getMock('Observer') ;
        $obs->expect($this->once()) 
            ->method('returnFooIfBar') 
            ->with($this->equlTo('bar')) ;

        $subj = new Subject($obs);
        $returnString= $subj->TradeStrings('bar') ;

        $this->assertEqual('foo', $returnString) ;
    }
}

据我了解,这个测试的目的是:
  1. 调用Observer::getFooIfBar一次。
  2. Observer::getFooIfBar得到字符串'bar'。 3.该方法按照类中的定义正常工作,并返回字符串'foo'。
据我理解,除了构造函数/自动加载未运行外,原始类的任何功能都未更改。
在运行getMock()时,如果我模拟一个方法,则模拟对象的方法在我指定它时才返回某些内容。
$obs = $this->getMock('Observer', array('returnFooIfBar'));
$obs->expects($this->once())
    ->method('returnFooIfBar')
    ->with('bar')
    ->will($this->returnValue('foo');

我理解得对吗?如果不是,请您澄清一下,因为我希望能更加清楚明白。 :)
编辑:修改了帖子以更清晰地表达我的需求和目前的理解。
2个回答

7
如果您让phpunit创建一个mock对象,它会内部构建一个新的临时类,该类继承原始类并使用特定于mock的代码实现此类的所有方法。
这背后的想法是在测试用例中解耦对象。虽然您的示例有效,但您不会以这种方式使用它。但是,您的示例测试无论如何都会失败,因为mock函数returnStringFooIfBar不会返回任何内容。
这是它应该工作的方式:
$obs = $this->getMock('observer') ;
$obs->expect($this->once()) 
    ->method('returnStringFooIfBar') 
    ->with($this->equlTo('bar'))
    ->will($this->returnValue('foo'));

$returnString= $obs->returnStringFooIfBar ('bar') ;
$this->assertEqual('foo', $returnString) ;

但是一个真实的测试案例需要涉及一些对象进行测试:
class TestObject {

    private $observer;
    public function __construct($observer) {
        $this->observer = $observer;
    }

    public function doMagicAndNotify() {
        // do heavy magic

        //notify observer
        $obsresult = $this->observer->returnStringFooIfBar('bar');

        return 'didmagic';
    }
}

class TestObjectTest extends PHPUnit_Framework_TestCase {

    public function testObserverCalling() {
        $obs = $this->getMock('observer') ;
        $obs->expect($this->once()) 
            ->method('returnStringFooIfBar') 
            ->with($this->equlTo('bar'));

        $test = new TestObject($obs);
        $returnString= $test->doMagicAndNotify() ;

        $this->assertEqual('didmagic', $returnString) ;
    }
}

编辑:

我想要的是一个正常工作的观察者,但要确保方法被正确的调用。

据我理解,原始类的任何功能都没有改变,除了构造函数/自动加载未运行之外。

实际上与之相反。Observer的临时子类覆盖了所有(或指定)方法并更改了原始功能(模拟方法的父类根本不被执行)。 它不覆盖构造函数,不管怎样都会被调用。

不可能断言模拟方法的方法调用,并在同时调用其原始方法。

请参考方法模板生成器

请记住:您在这里没有测试观察员的正确行为,而是模拟其行为以测试主题。

注释:$this->returnCallback($someCallback)是一个强大的函数,可能会对您有所帮助。 我不喜欢这个想法,但您可以执行以下操作:

public function testObserverCalling() {
    $obs = new Observer();
    $obsmock = $this->getMock('observer') ;
    $obsmock->expect($this->once()) 
        ->method('returnStringFooIfBar') 
        ->with($this->equlTo('bar'))
        ->will($this->returnCallback(array($obs, 'returnStringFooIfBar')));

    $test = new TestObject($obsmock);
    $returnString= $test->doMagicAndNotify() ;

    $this->assertEqual('didmagic', $returnString) ;
}

1
+1 - 我已经准备好了几乎完全相同的帖子,但你比我更快地发布了它 :-) - cmbuckley
我已经修改了我的帖子,试图澄清我的意思以及我目前的理解。 - Penetal
谢谢,这帮助我更好地理解了模拟对象的工作原理。对我来说最重要的信息是,模拟只按照指定的方式工作,并且不保留任何原始功能。我知道我的请求很丑陋,因为它包含了测试主题。只是不想每次都麻烦地指定正确的返回值,但我想这也一样好。让我立即在我的测试中养成良好的习惯。 - Penetal

2

现在你可以在phpunit中使用enableProxyingToOriginalMethods()来实现这个功能。

class SubjectTest extends \PHPUnit\Framework\TestCase
{
    public function testCallsObsMethod()
    {
        $obs = $this->getMockBuilder('Observer')
          ->enableProxyingToOriginalMethods()
          ->getMock();
        $obs->expect($this->once()) 
          ->method('returnFooIfBar');

        $subj = new Subject($obs);
        $returnString= $subj->tradeStrings('bar') ;

        $this->assertEquals('foo', $returnString) ;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接