2

I'm trying to mock out the actual database operations in my Laravel application for the purposes of effective unit testing. But I am having difficulty in getting the mocked method to be called, rather than the real method.

I have about 200 Models in app/Models which were generated using artisan make:model. I'm in the process of creating query builder classes which extend Illuminate\Database\Eloquent\Builder for each of the models. Likewise, there will be a PHPUnit test class for each of these query builders.

I've spent the last 2 days with the Laravel docs, the Mockery docs, Stack Overflow, Google and lots of debugging trying to get my unit test to work correctly, to no avail. What's the trick to making this work with Laravel's intricate internals?

Example Model, stripped down:

<?php
namespace App\Models;
use App\Domain\Example\QueryBuilders\ExampleQueryBuilder;
use Illuminate\Database\Eloquent\Model;
/**
 * @property int $id
 * @property string $foo
 * @property string $bar
 * @property string $baz
 */
class Example extends Model
{
    public $timestamps = false;
    protected $table = 'my_table';
    protected $fillable = ['foo', 'bar', 'baz',];
    protected $dateFormat = 'U';
    protected $connection = 'mysql';
    public function newEloquentBuilder($query): ExampleQueryBuilder {
        return new ExampleQueryBuilder($query);
    }
}

Example query builder, stripped down:

<?php
namespace App\Domain\Example\QueryBuilders;
use App\Models\Example;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
final class ExampleQueryBuilder extends Builder
{
    protected $model;
    protected $onDelete;
    protected $query;

    public function findActive(string $foo): ?Model {
        $conditions = ['foo' => "$foo", 'bar' => 1,];
        $example = Example::firstWhere($conditions);
        if ($example instanceof Example) {return $example;}
        return null;
    }

Example unit test:

<?php
namespace Tests\Domain\Example\QueryBuilders;
use App\Models\Example;
use Tests\TestCase;

class ExampleQueryBuilderTest extends TestCase
{
    /** @test */
    public function it_handles_missing_example_correctly(): void {
        // I've tried endless variations on this, mocking the Model "Example",
        // mocking "Model", even "Process" at one crazy point.  Debugging proves
        // without a doubt that the `Builder::firstWhere` method IS being called
        // instead of my mocked method.
        $mock = $this->partialMock(Builder::class, function (MockInterface $mock) {
            $mock->shouldReceive('firstWhere')->once();
        });
        $this->app->instance(Builder::class, $mock);

        $example = Example::findActive('');
        self::assertNull($example);
    }
}

When this test is run, I get this output:

Method firstWhere() from Mockery_0_Illuminate_Database_Eloquent_Builder should be called exactly 1 times but called 0 times.

So clearly the method I need to mock out is not getting mocked somehow. What am I overlooking or missing?

My environment: Laravel 8, Mockery 1.4, PHPUnit 9, PHP 7.4/8.0, macOS/Linux

CXJ
  • 4,301
  • 3
  • 32
  • 62
  • I suggest you construct the builder in your model using `app()->makeWith(ExampleQueryBuilder::class, [ 'query' => $query ]);` and then in your test setup replace the example builder in the container using `app()->offsetSet(ExampleQueryBuilder::class, $mock);` (the last bit is a workaround discussed in [this issue](https://github.com/laravel/framework/issues/25041#issuecomment-445479867) . Not sure if this would work so if it does do answer the question with what you did since it might be helpful – apokryfos Feb 20 '21 at 19:23
  • Sidenote, your test is called `ExampleQueryBuilderTest` so I was expecting it to be testing the `ExampleQeuryBuilder` class itself directly, maybe your test should be called `ExampleModelTest` – apokryfos Feb 20 '21 at 19:25
  • I'm definitely trying to test `ExampleQueryBuilder` not the Model. I assume that Laravel's database stuff has been thoroughly tested and I shouldn't need to test it. ;-) I'll take a look at your first suggestion and see what happens. Thanks. – CXJ Feb 21 '21 at 15:37
  • If you're testing the builder then do it directly and not through `Example` though now that I see it again it seems a bit roundabout `findActive` calls `Example::firstWhere` which in turn calls `newEloquentBuilder` to forward the `firstWhere` to but since you're extending builder then you can just call `$this->firstWhere` – apokryfos Feb 21 '21 at 17:20
  • No luck in getting it to work using the above suggestions.. It's entirely possible I did not correctly understand them. – CXJ Feb 21 '21 at 22:55
  • This might help: https://stackoverflow.com/questions/35001878/how-do-i-mock-the-db-facade-in-laravel – Mark Joshua Fajardo Jun 30 '22 at 01:46

0 Answers0