Chainable methods and Function Mocker

A useful addition to Function Mocker.

The problem

Mocking self returning methods used in client classes is possible in PHPUnit using a not very complex syntax; given the two classes below

class QueryClient {

    protected $query;

    public function setQuery(Query $query){
        $this->query = $query;
    }

    public function findOne($id){
        $entry_data = $this->query
            ->where('id', '=', $id)
            ->where('type', 'in', ['post', 'comment'])
            ->where('status', '=', 'open');
            ->get();

        return Entry::createFrom($entry_data);
    }

}

class Query {

    public function where($column, $condition, $constraint){
        ...

        // self-returning to allow for method chaining
        return $this;
    }

    public function get(){
        ...
    }

}

the Client::findOne method can be tested like this

public function test_findOne(){
    $mockPost = $this->getMockPost();
    $mockQuery = $this->getMock('Query');

    $mockQuery->expects($this->at(0))
        ->method('where')
        ->with('id', '=', 23)
        ->willReturn($mockQuery);
    $mockQuery->expects($this->at(1))
        ->method('where')
        ->with('type', 'in', ['post', 'comment'])
        ->willReturn($mockQuery);
    $mockQuery->expects($this->at(2))
        ->method('where')
        ->with('status, '=', 'open')
        ->willReturn($mockQuery);
    $mockQuery->expects($this->once())
        ->method('get')
        ->willReturn($mockPost);

    $sut = new QueryClient();
    $sut->setQuery($mockQuery);
    $entry = $sut->findOne(23);

    $this->assertInstanceOf('Entry', $entry);
}

and while lengthy it will work. I wanted the same to be possible using function-mocker too.

Self returning replacements

Given the two classes above the same checks can be made using function-mocker like this

use tad\FunctionMocker\FunctionMocker as Test; 

public function test_findOne_using_FunctionMocker(){
    // prepare
    $mockPost = $this->getMockPost();
    Test::replace('Query::where', '->');
    $mockQuery = Test::replace('Query::get', $mockPost);

    // execute
    $sut = new QueryClient();
    $sut->setQuery($mockQuery);
    $entry = $sut->findOne(23);

    // verify 
    // argument order does not matter!
    $expectedArgs = [
        ['id', '=', 23],
        ['type', 'in', ['post', 'comment']],
        ['status', '=', 'open']
    ];
    foreach($expectedArgs as $arg){
        $mockQuery->wasCalledWithOnce($arg, 'where');
    }
    $this->assertInstanceOf('Entry', $entry);
}

It’s shorter and I like having verification all in the same place. I’ve pushed the change to the library to GitHub.

Next

I will be adding more tests to the package, do some refactoring and look into a way to allow callback function based return values to get access to the self object.

I appreciate your input