29

我正在尝试在 setUp 中创建一个模拟实例,其中所有覆盖方法的默认值,然后在几个不同的测试中根据我正在测试的内容更改某些方法的返回值,而无需设置整个 Mock . 有没有办法做到这一点?

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

第一个设置:

$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 稍后更改期望,但没有得到回应,我认为一个新问题可能会将问题带到前台。

4

6 回答 6

11

如果您多次使用相同的方法,则应在代码中执行的位置使用具有正确计数的“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');
    }

我想这就是你要找的东西,但如果我有误解,请告诉我。

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

于 2012-12-21T03:36:46.067 回答
6

您可以使用 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

于 2014-05-30T12:13:49.837 回答
2

一种方法是使用数据提供者,例如:

/**
 * @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)
  );
}

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

于 2012-11-29T20:20:21.147 回答
1

我没有尝试过,但你能不能在设置中设置模拟,然后在每个测试中:

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

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

于 2012-11-30T14:47:59.150 回答
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);
    }
}
于 2014-09-18T09:59:57.933 回答
-1

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

/**
 * @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,但模式是一样的。因为每个测试每次运行都有新鲜的记忆,所以我们不会遇到“覆盖”先前设定的期望的限制。

于 2015-01-20T16:12:34.493 回答