10
votes

I need to test, that the code creates a new instance of a class with certain parameters:

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

I tried to create an alias mock and set an expectation for the __construct method, but it didn't work:

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

This expectation is never met:

Mockery\Exception\InvalidCountException: Method __construct(object(Mockery\Matcher\Type), 3) from Symfony\Component\Console\Helper\ProgressBar should be called exactly 1 times but called 0 times.

Do you know any other way how to test this with Mockery?

1
Does your constructor contain business logic? If it does then it probably shouldn't, because constructors should only really be concerned with initialising an object into a valid initial state. This is normally little more than assigning the arguments to member variables which is simple enough to not really require testing.GordonM
I'm not quite sure if understand your question correctly. Do you want to create a Mock but don't know how to pass constructor arguments? Or are you really trying to check if the constructor method is called? Because that would be really, really bad practice - that's just not how it works. Or are you testing a method which creates a ProgressBar object? Then you should refactor it so that the PrograssBar-object is passed as a parameter either to that method or as a constructor argument (Dependency Injection) of the class containing that method.Quasdunk
I'm writing a simple command for Symfony Console. I wanted to test that a ProgressBar with correct number of units was initialized during execution of the command.VaclavSir
@VaclavSir Ok, this is not quite possible with e.g. PhpUnit. There is a thing called "aspect mock" that allows for scenarios like this, but just because you could doesn't mean you should. As I mentioned above, please read about "Dependency Injection". The OOP way to do this would be to pass the PrograssBar-object to the method instead of creating it within the method.Quasdunk
No it wouldn't, the DI way would be to inject a factory. However it's a common practice to instantiate ProgressBar directly and the factory wouldn't do anything else than return new ProgressBar(...func_get_args()). So I was wondering if this practice could be tested with Mockery. I will have a look at AspectMock.VaclavSir

1 Answers

1
votes

Well you cannot mock constructor. Instead you need slightly modify your production code. As I can guess from decription you have something like this:

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

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

Which is not best code in the world, because we have coupled dependance. (Which is totally ok if ProgressBar is Value Object for example).

First of all you should to test ProgressBar separately from Foo. Because then you test Foo you do not need care how ProgressBar works. You know it works, you have tests for this.

But if you still want to test it instantiation (for any reason) here is two ways. For both ways you need extract new ProggresBar

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

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

Way 1:

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

Way 2:

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

Happy testing!