2012-11-29 157 views
12

我想在setUp中用所有重写的方法的默认值创建一个模拟实例,然后在几个不同的测试中更改某些方法的返回值,具体取决于我测试的内容而无需设置整个模拟。有没有办法做到这一点?我可以在设置PHPUnit模拟器后更改方法吗?

这是我试过的,但天真的方法不起作用。该方法仍会返回原始期望设置的值。

首次设置:

$my_mock->expects($this->any()) 
     ->method('one_of_many_methods') 
     ->will($this->returnValue(true)); 

在另一个试验不同的断言前:

$my_mock->expects($this->any()) 
     ->method('one_of_many_methods') 
     ->will($this->returnValue(false)); 

复制到这个问题:PHPUnit Mock Change the expectations later,但一个没有得到答复,我想到了一个新的问题可能带来的这个问题脱颖而出。要做到这一点

+0

AFAIK不幸的是,phpunit没有这种可能性。你可以使用例如$ my_mock - > __ phpunit_hasMatchers(),但它不完全是你想要的。当然你可以用a)“at”匹配器或b)“returnCallback”在同一个方法上设置不同的返回值,但它们依赖于a)调用顺序b)调用参数..但也不是你要找的。我会让你知道我找出新的东西。 – Cyprian

+0

另请参阅http://stackoverflow.com/questions/5484602/mock-in-phpunit-multiple-configuration-of-the-same-method-with-different-argum/5484864#5484864 – bishop

回答

3

一种方法是使用一个data provider,是这样的:

/** 
* @dataProvider methodValueProvider 
*/ 
public function testMethodReturnValues($method, $expected) { 
    // ... 
    $my_mock->expects($this->any()) 
      ->method($method) 
      ->will($this->returnValue($expected)); 
    // ... 
} 

public function methodValueProvider() { 
    return array(
    array('one_of_many_methods', true), 
    array('another_one_of_many', false) 
); 
} 

编辑:对不起,我误解你的问题。以上(显然)不是你要找的。

1

我没有试过,但你能不能设置模拟了在安装程序,然后在每个测试的:

public function testMethodReturnsTrue 
{ 
    $this->my_mock->will($this->returnValue(true)); 
    $this->assertTrue(...); 
    ... 
} 

我不知道这是否会工作,因为我想在测试中设置will()方法,而不是在初始模拟创建时。

5

在多次使用相同方法的情况下,应在代码中执行时使用“at”声明以及适当的计数。这样PHPUnit就知道你的意思,并且可以正确地完成期望/断言。

下面是一个普通的例子,其中法“跑”被多次使用:

public function testRunUsingAt() 
    { 
     $test = $this->getMock('Dummy'); 

     $test->expects($this->at(0)) 
      ->method('run') 
      ->with('f', 'o', 'o') 
      ->will($this->returnValue('first')); 

     $test->expects($this->at(1)) 
      ->method('run') 
      ->with('b', 'a', 'r') 
      ->will($this->returnValue('second')); 

     $test->expects($this->at(2)) 
      ->method('run') 
      ->with('l', 'o', 'l') 
      ->will($this->returnValue('third')); 

     $this->assertEquals($test->run('f', 'o', 'o'), 'first'); 
     $this->assertEquals($test->run('b', 'a', 'r'), 'second'); 
     $this->assertEquals($test->run('l', 'o', 'l'), 'third'); 
    } 

我认为这是你在找什么,但如果我误解,请让我知道。

现在就嘲笑任何事情而言,您可以随意多次嘲笑它,但是您不想嘲笑它与设置中的名称相同,否则每次使用它时都会引用到设置。如果你需要在不同的场景中测试类似的方法,那么为每个测试进行模拟。您可以在设置中创建一个模拟,但是对于一次测试,在单个测试中使用不同的模拟类似项目,但不是全局名称。在use声明

$one_of_many_methods_return = true; 
$my_mock->expects($this->any()) 
     ->method('one_of_many_methods') 
     ->will(
      $this->returnCallback(
       function() use (&$one_of_many_methods_return) { 
        return $one_of_many_methods_return; 
       } 
      )   
     ); 
$this->assertTrue($my_mock->one_of_many_methods()); 

$one_of_many_methods_return = false; 

$this->assertFalse($my_mock->one_of_many_methods());  

注意&

+5

我认为这应该是被接受的答案,但是请注意,用于at()的索引取决于对该模拟对象的调用总数,而不是针对该特定方法的调用数。因此,如果对同一对象上的其他方法进行其他调用,则需要相应地增加索引。 – Russ

3

你可以做到这一点使用一个lambda回调。

+0

这对于一次性值很有用,尽管测试方法比引用('&$ one_of_many_methods_return')更容易共享属性('$ this-> one_of_many_methods_return')。你也可以直接使用方法,比如'$ this-> returnCallback([$ this,'someMethod'])''。还要注意PHP <5.5可能会抱怨在匿名函数中使用'$ this'和private/protected属性/方法。 – Warbo

1

与其试图重写嘲笑的方法,我发现重写嘲笑的对象本身更容易。例如:

class ThingTest extends \PHPUnit_Framework_TestCase 
    public function setUp() 
    { 
     $this->initFoo(); 
     $this->initBar(); 
    } 

    public function testOne() 
    { 
     // Uses default [method => value] map for foo and bar 
     $this->assertSomething($this->thing->someMethod()); 
    } 

    public function testTwo() 
    { 
     // Override foo's map 
     $this->initFoo(['method1' => 'some other value']); 
     $this->assertSomethingElse($this->thing->someMethod()); 
    } 

    public function testThree() 
    { 
     // Override bar explicitly, so we can use 'once' 
     $this->initBar([]); 
     $this->bar->expects($this->once()) 
        ->method('method1'); 
     $this->thing->someOtherMethod(); 
    } 

    private function initFoo($methods = null) 
    { 
     $this->init('foo', 
        $this->getMock('Foo'), 
        is_null($methods)? ['method1' => 'default value 1'] 
            : $methods); 
    } 

    private function initBar($methods = null) 
    { 
     $this->init('bar', 
        $this->getMock('Bar'), 
        is_null($methods)? ['method1' => 'default value 1'] 
            : $methods); 
    } 

    private function init($name, $object, $methods) 
    { 
     $this->$name = $object; 
     foreach ($methods as $method => $value) { 
      $this->$name->expects($this->any()) 
         ->method($method) 
         ->will($this->returnValue($value)); 
     } 
     $this->thing = new Thing($this->foo, $this->bar); 
    } 
} 
0

您还可以在单​​独的进程中运行测试:

/** 
* @runTestsInSeparateProcesses b/c we change the return value of same expectation 
* @see http://stackoverflow.com/questions/13631855 
*/ 
class ThingTest extends \PHPUnit_Framework_TestCase 
{ 
    public function setUp() { 
     $this->config = Mockery::mock('alias:Config'); 
    } 

    public function test_thing_with_valid_config() { 
     $this->config_set('default', 'valid'); 
     $sut = new \Thing(); 
    } 

    /** 
    * @dataProvider provides_broken_configs 
    * @expectedException \RuntimeException 
    */ 
    public function test_thing_with_broken_config($default) { 
     $this->config_set('default', $default); 
     $sut = new \Thing(); 
    } 

    public function provides_broken_configs() { 
     return [ [ null ] ]; 
    } 

    protected function config_set($key, $value) { 
     $this->config->shouldReceive('get')->with($key)->andReturn($value); 
    } 
} 

在这个例子中,我碰巧使用嘲弄,但是模式是一样的。由于每个测试都有新的记忆,所以我们不会遇到“超越”以前设定的期望的限制。

相关问题