Greg
Greg

Reputation: 6623

phpunit with mockery - how to test a mocked object is constructed with proper arguments

So I'm writing tests for a php api consumer library. In one of the main libraries main functions I have:

public function __call($name, $args) {
    return new Schema($name, $this);
}

In my test I'm using mockery and doing something like:

$schemaMock = m::mock('overload:Mynamespace\Schema');

this is properly overloading my Schema class with a mock. So later when I do:

$myclass->movies()

It should call the __call method, thus calling the mocked Schema class. This all seems good so far, but I would like to assert that the $schemaMock is being constructed with the name of the function, in this case movies as well as the instance of the class being passed in. What I've tried is:

$schemaMock->shouldReceive('__construct')->with('movies');

However my tests pass regardless of what the "with" function argument states. IE I can change movies to foobar and tests still pass. I'm sure I'm missing something simple about how to run these assertions. Thanks for any help!

Upvotes: 1

Views: 1546

Answers (1)

ccprog
ccprog

Reputation: 21836

Short and final answer: you can't.

A mock is defined by cobbling together code for a new class. It contains methods for all mocked methods, and for all others, it is a child class that implements the original class as its parent. The mock class contains no constructor - for normal mock objects, the parent gets called. That is the way constructor arguments passed to Mockery::mock($class, $args) get considered.

But for instance mocks, a constructor for copied mocks is needed. It is hidden pretty deep inside the source, but if you look at mockery/library/Mockery/Generator/StringManipulation/Pass/InstanceMockPass.php, you can see that the only purpose of that method is to copy properties and expectations over to the new instance. Arguments are simply ignored.

The best approximation you could get would be to wrap your instantiation call in a mockable method that does nothing besides creating an instance with the arguments from the method - like here:

public function instantiate ( $name, $args=array() ) {
    if ( empty($args) )
        return new $name();
    else {
        $ref = new ReflectionClass( $name );

        return $ref->newInstanceArgs( $args );
    }
}

or to delegate the real instantiation task from the constructor to a mockable method:

class Schema {

    public function __construct () {
        call_user_func_array( array( $this, 'init' ), func_get_args() );
    }

    public function init ($name, $obj) {
        //...
    }
}

Upvotes: 0

Related Questions