我在设置之后可以在PHPUnit Mock上更改方法吗?

时间:2012-11-29 18:00:23

标签: php mocking phpunit

我正在尝试使用所有重写方法的默认值在setUp中创建一个模拟实例,然后在几个不同的测试中根据我正在测试的内容更改某些方法的返回值而不必设置整个模拟。有没有办法做到这一点?

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

首先设置:

$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,但那个没有回复,我认为一个新问题可能会把问题放在首位。

6 个答案:

答案 0 :(得分:7)

如果您多次使用相同的方法,则应使用“at”声明,并在代码中执行正确的计数。这样PHPUnit知道你的意思,并且可以正确地满足期望/断言。

以下是多次使用方法“run”的一般示例:

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

我认为这就是你要找的东西,但如果我误会请告诉我。

现在,在模拟任何东西方面,你可以根据需要多次模拟它,但你不想用与设置中相同的名称来模拟它,否则每次你使用它时你指的是设置。如果您需要在不同的场景中测试类似的方法,那么为每个测试模拟它。您可以在设置中创建一个模拟,但对于一个测试,在单个测试中使用不同的类似项目模拟,但不使用全局名称。

答案 1 :(得分:4)

您可以使用lambda回调来执行此操作:

$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());    

请注意&声明中的use

答案 2 :(得分: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)
  );
}

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

答案 3 :(得分:1)

我没试过这个,但是你不能在安装程序中设置Mock,然后在每个测试中:

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

我不确定这是否有效,因为我试图在测试中设置will()方法,而不是在创建初始模拟时。

答案 4 :(得分: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);
    }
}

答案 5 :(得分: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);
    }
}

在这个例子中,我碰巧使用的是Mockery,但模式是一样的。因为每个测试都有新鲜的记忆,所以我们都没有遇到过#" overriding"以前设定的期望。