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