I'm writing a unit test to filter out invalid relations from a model. The goal is to only keep relations that actually exist in the model. However, my test is failing because the filtered relations collection is coming back empty, even though I've mocked one of the relations validRelation
to return a valid HasMany relationship.
Additional Context:
I'm using Laravel and Mockery for unit testing.
The filterRestrictedRelations
method is protected, so I'm invoking it via reflection.
I have tried to set the method to public
and ommit the reflection part but still the collection is coming back empty - It looks like the issue lies within how Mockery is handling the shouldReceive('validRelation')
What could be causing this behavior, and how can I properly test that valid relations are not filtered out?
protected function filterRestrictedRelations(): void
{
$this->restrictedRelations = $this->restrictedRelations->filter(function ($relation) {
$exists = method_exists($this->model, $relation);
dump("Checking relation: $relation, exists: " . ($exists ? 'true' : 'false'));
return $exists;
});
}
Here's the relevant part of my test:
public function setUp(): void
{
parent::setUp();
$this->model = \Mockery::mock(AnAbstractClass::class)->makePartial();
$this->modelRelation = \Mockery::mock(new ModelRelation($this->model));
$this->reflectionClass = new ReflectionClass($this->modelRelation);
}
public function it_filters_out_relations_that_do_not_exist_in_model(): void
{
$relations = ['validRelation', 'invalidRelation1', 'invalidRelation2'];
// Mocking model to have the 'validRelation' method returning a HasMany relation
$this->model->shouldReceive('validRelation')
->andReturn(\Mockery::mock(HasMany::class));
$this->modelRelation = new ModelRelation($this->model);
$this->modelRelation->setRestrictedRelations($relations);
$this->reflectionClass = new ReflectionClass($this->modelRelation);
// Invoke the filterRestrictedRelations method
$this->reflectionClass->getMethod('filterRestrictedRelations')
->invoke($this->modelRelation);
$filteredRelations = $this->reflectionClass->getMethod('getRestrictedRelations')->invoke($this->modelRelation);
// Check that invalidRelation1 remains, while others are filtered out
$this->assertEquals(['validRelation'], $filteredRelations->all());
}
When using Mockery to mock an existing class, the method_exists()
function will return true for any method that actually exists on the original class, but it will return false for methods that don't exist, even if you use shouldReceive()
.
This happens because shouldReceive()
does not create a method in the conventional way; rather, it leverages the __call()
magic method for method overloading.
Mockery offers a method to verify if a mock is expecting a specific method call. Rather than using method_exists()
, you can determine if a mock has any expectations set for that particular method.
protected function filterRestrictedRelations(bool $mocking = false): void
{
$this->restrictedRelations = $this->restrictedRelations->filter(function ($relation) {
return method_exists($this->model, $relation) || ($mocking && $this->model->mockery_getExpectationsFor($relation));
});
}