Как издеваться над конструктором с помощью Mockery

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

$bar = new ProgressBar($output, $size);

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

$progressBar = \Mockery::mock('alias:' . ProgressBar::class);
$progressBar->shouldReceive('__construct')
    ->with(\Mockery::type(OutputInterface::class), 3)
    ->once();

Это ожидание никогда не оправдывается:

Mockery \ Exception \ InvalidCountException: метод __construct (object (Mockery \ Matcher \ Type), 3) из Symfony \ Component \ Console \ Helper \ ProgressBar должен вызываться ровно 1 раз, но вызываться 0 раз.

Вы знаете другой способ проверить это с помощью Mockery?


person VaclavSir    schedule 01.06.2015    source источник
comment
Ваш конструктор содержит бизнес-логику? Если это так, то, вероятно, не должно, потому что конструкторы должны действительно заботиться только об инициализации объекта в допустимое начальное состояние. Обычно это немного больше, чем присвоение аргументов переменным-членам, что достаточно просто, чтобы на самом деле не требовать тестирования.   -  person GordonM    schedule 01.06.2015
comment
Я не совсем уверен, правильно ли понял ваш вопрос. Вы хотите создать макет, но не знаете, как передать аргументы конструктора? Или вы действительно пытаетесь проверить, вызывается ли метод конструктора? Потому что это было бы действительно очень плохой практикой - это просто не то, как это работает. Или вы тестируете метод, который создает объект ProgressBar? Затем вы должны реорганизовать его, чтобы объект PrograssBar передавался в качестве параметра либо этому методу, либо как аргумент конструктора (внедрение зависимостей) класса, содержащего этот метод.   -  person Quasdunk    schedule 01.06.2015
comment
Пишу простую команду для Symfony Console. Я хотел проверить, что ProgressBar с правильным количеством единиц был инициализирован во время выполнения команды.   -  person VaclavSir    schedule 02.06.2015
comment
@VaclavSir Хорошо, это невозможно, например, PhpUnit. Существует вещь, называемая имитацией аспекта, которая допускает такие сценарии, но то, что вы могли, не означает, что вы должны. Как я уже упоминал выше, прочтите, пожалуйста, о внедрении зависимостей. ООП способ сделать это - передать объект PrograssBar в метод, а не создавать его внутри метода.   -  person Quasdunk    schedule 02.06.2015
comment
Нет, это не так, способ DI заключался бы в том, чтобы ввести фабрику. Однако обычно создается экземпляр ProgressBar напрямую, и фабрика не будет делать ничего, кроме return new ProgressBar(...func_get_args()). Поэтому мне было интересно, можно ли эту практику протестировать с помощью Mockery. Я посмотрю на AspectMock.   -  person VaclavSir    schedule 04.06.2015
comment
Не идеально подходит для тестов, но иногда единственный способ протестировать магические методы, макетировать, расширять класс-тест. Class TestProgressBar Extends ProgrssBar{ public function __construct( ... ){ assert };   -  person Tony Chiboucas    schedule 27.10.2017


Ответы (1)


Ну вы не можете издеваться над конструктором. Вместо этого вам нужно немного изменить свой производственный код. Как я могу догадаться из описания, у вас есть что-то вроде этого:

class Foo {
    public function bar(){
        $bar = new ProgressBar($output, $size);
    }
}

class ProgressBar{
    public function __construct($output, $size){
        $this->output = $output;
        $this->size = $size;
    }
}

Это не лучший код в мире, потому что у нас есть двойная зависимость. (Что совершенно нормально, например, если ProgressBar является объектом значения).

Прежде всего вам следует протестировать ProgressBar отдельно от Foo. Поскольку тогда вы проверяете Foo, вам не нужно заботиться о том, как ProgressBar работает. Вы знаете, что это работает, у вас есть тесты для этого.

Но если вы все еще хотите протестировать его создание (по любой причине), есть два способа. Для обоих способов вам нужно извлечь new ProggresBar

class Foo {
    public function bar(){
        $bar = $this->getBar($output, $size);
    }

    public function getBar($output, $size){
        return new ProgressBar($output, $size)
    }
}

Способ 1:

class FooTest{
    public function test(){
        $foo = new Foo();
        $this->assertInstanceOf(ProgressBar::class, $foo->getBar(\Mockery::type(OutputInterface::class), 3));
    }
}

Способ 2:

class FooTest{
    public function test(){
        $mock = \Mockery::mock(Foo::class)->makePartial();
        $mock->shouldReceive('getBar')
            ->with(\Mockery::type(OutputInterface::class), 3)
            ->once();
    }
}

Удачного тестирования!

person Alexander Matrosov    schedule 29.12.2017