Подтвердить что ты не робот

Могу ли я изменить метод на PHPUnit Mock после того, как я его установил?

Я пытаюсь создать экземпляр mock в 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 Измените ожидания позже, но у этого нет ответов, и я думал, что новый вопрос может поставить проблему на передний план.

4b9b3361

Ответ 1

В тех случаях, когда вы используете один и тот же метод более одного раза, вы должны использовать объявление "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');
    }

Я думаю, что это то, что вы ищете, но если я не понимаю, пожалуйста, дайте мне знать.

Теперь с точки зрения насмешек что угодно, вы можете издеваться над ним столько раз, сколько хотите, но вы не собираетесь издеваться над ним с тем же именем, что и в настройке, иначе каждый раз, когда вы его используете, вы имеете в виду настройки. Если вам нужно протестировать аналогичные методы в разных сценариях, то издевайтесь над ними для каждого теста. Вы можете создать один макет в настройке, но для одного теста используйте другой макет аналогичного элемента в отдельном тесте, но не глобального имени.

Ответ 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)
  );
}

Изменить: Извините, я неправильно понял ваш вопрос. Вышеуказанное (очевидно) не то, что вы ищете.

Ответ 3

Вы можете сделать это, используя обратный вызов лямбда:

$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.

Ответ 4

Я не пробовал это, но не мог ли вы установить Mock в Setup, а затем в каждом из тестов:

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

Я не уверен, что это сработает, поскольку я пытаюсь установить метод will() в тесте, а не когда начальный макет был создан.

Ответ 5

Вместо того, чтобы пытаться переопределить посмеянные методы, мне легче переопределить сами насмешливые объекты. Например:

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

Ответ 6

Вы также можете запускать тесты в отдельном процессе:

/**
 * @runTestsInSeparateProcesses b/c we change the return value of same expectation
 * @see http://stackoverflow.com/info/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, но шаблон тот же. Поскольку каждый тест имеет свежую память, каждый из которых проходит, мы не сталкиваемся с ограничением "переопределения" ранее установленных ожиданий.