myol myol - 1 month ago 9
PHP Question

Mocking and stubbing the same class instance using mockery

I have a class

myModel
which makes database calls. As such I want to stub this so it doesn't actually make any of these expensive calls.

public function myFunction($limit)
{
$this->doThing();
}

private function doThing()
{
$result = $this->myModel
->select('thing')
->groupBy('name')
->orderBy('count', 'desc')
->get;

// do stuff with $result
}


So to stub the models methods which make the call I use

/** @test */
public function my_test()
{
$stuff = new Collection(['person1', 'person2', 'person3',]);

$myModelMock = m::mock(MyModel::class, [
'select->groupBy->orderBy->get' => $stuff
]);

App::instance(MyModel::class, $myModelMock);
$myOtherClass = App::make(OtherClassWhereMyModelIsInjectedAutomagically::class);

$myOtherClass->myFunction();
}


Which works perfectly, outputting
$result
as the Collection of
$stuff
I define in the test.

However, I also want to ensure that the fluent interface functions are only called once. I understand these functions are being called inside a private method but that shouldn't matter as I am not testing the private function itself.

So when I try and use

/** @test */
public function query_ran_once()
{
$stuff = new Collection(['person1', 'person2', 'person3',]);

$myModelMock = m::mock(MyModel::class, [
'select->groupBy->orderBy->get' => $stuff,
'where->update' => null,
'whereIn->update' => null
]);

$myModelMock
->shouldReceive('select->groupBy->orderBy->get')
->times(1)

App::instance(MyModel::class, $myModelMock);
$myOtherClass = App::make(OtherClassWhereMyModelIsInjectedAutomagically::class);

$myOtherClass->myFunction();
}


I get an error which ends up being that
$result
is
null
- which means it is no longer being replaced by the
$stuff
data in my test.

How can the stub data be used before the mock expectations are run?

Answer

Because you have to add andReturn here:

$collection = m::mock(\Illuminate\Database\Eloquent\Collection::class)
$myModelMock
    ->shouldReceive('select->groupBy->orderBy->get')
    ->times(1)
    ->andReturn($collection);

In this case it's good to return mocked Collection or Collection.