0

I'm trying to write a PHPUnit test using Mockery, and I keep running into issues when using the null coalescing operator (??) with mocked properties. Specifically, ?? seems to always return null, which causes the ?? operator to fall back to the default value, even though the property is mocked correctly and returns a value. Here’s a simplified version of my setup:

public function testExample()
{
    $model = Mockery::mock(Model::class)->shouldIgnoreMissing();

    // Mocking a property
    $model->shouldReceive('getAttribute')
        ->with('title')
        ->andReturn('Test Page');

    $model->title = 'Test Page'; // I also tried this

    $result = $this->doSomething($model);

    $this->assertEquals('/test-page', $result);
}

public function doSomething($model): string
{
    print_r([$model->title, $model->title ?? 'default']);
    ...
    ...
}

Output:

Array
(
    [0] => Test Page
    [1] => default
}

When I use $model->title directly, it works fine and returns the mocked value ('Test Page'). However, when I try to use $model->title ?? 'default', the fallback ('default') is always returned, as if the title property doesn't exist or is null.

Is there a way to make the null coalescing operator (??) work reliably with Mockery mocks in PHP?

Stack: PHP 8.2, PHPUnit 10.5 with Laravel 11.33.2

Note: I'm not trying to test the model itself or any part of Laravel's framework. My goal is simply to make the model return a specific value in a practical way without setting up factories its relations.

0

2 Answers 2

1

I think you're using the Model mock somewhat incorrectly by mocking getAttribute(). What often happens in tests is that you use a Laravel factory to create some mock model like $model = User::factory()->create(), which should set the properties correctly.

The behaviour you are seeing might be coming from the fact that internally in Eloquent models, all properties are actually part of the protected array $attributes array and due to some magic __get() they are retrieved.

So I believe that if you normally construct the mock object (so using YourModel::create() call through a factory or manually in a test) it should work as intended.

EDIT: now that I am writing this out you might be able to get away with mocking the protected $attributes property on the Eloquent model and set a property that way. But it is somewhat odd to do it that way.

Sign up to request clarification or add additional context in comments.

Comments

0

I was able to solve the issue by using a factory as suggested by Flame, rather than solely relying on mocking. Here's how I implemented it:

Instead of this approach:

$model = Mockery::mock(Model::class)->shouldIgnoreMissing();
$model->shouldReceive('getAttribute')
      ->with('title')
      ->andReturn('Test Page');

$model->title = 'Test Page';

I used a factory to create the model with the necessary attributes before applying the mock:

$model = MyModel::factory()->make([
    'title' => 'Test Page'
]);

$model = Mockery::mock($model)->shouldIgnoreMissing();

This approach allowed the null coalescing operator (??) to work correctly with the mocked object while maintaining the expected behavior for getAttribute.

Thanks to Flame's answer for pointing me in the right direction!

2 Comments

For anyone trying to do the same, eventhough it works, it is not a "correct" solution, you should never (I would say you must NOT) mock a model, as it has a lot of magic inside it. You must either use a factory to have the data you expect or don't mock anything, because, Laravel already tested everything a model has, so you are re testing internal logic, you are testing the framework not your code. And if you need to test your code, instead of mocking the model, if it has a especial method, mock the dependency of that method, not the model's method due to Laravel magic
I agree that testing models is often unnecessary. In my case, I needed the model to return a specific value and wasn’t trying to test the Laravel framework. Mocking was simply easier than using factories and setting up all the expected relationships. While not ideal in every case, sometimes practicality outweighs strict adherence to best practices. I tried to make the example in the question and answer as simple as possible.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.