Mocking callbacks in Laravel 4 (Mockery)
Asked Answered
W

1

6

I'm currently writing tests for a package in Laravel 4.

I am mocking the Illuminate\Database\Query\Builder which works nearly all the time except when a where method uses a callback, I cannot check if the methods inside the callback are called.

I was hoping one of you could shed some light.

$query = \Mockery::mock('Illuminate\Database\Query\Builder', function ($mock) {
    /** @var \Mockery\Mock $mock */
    $mock->shouldReceive('where');
    $mock->shouldReceive('orWhere')->twice();
});

And the actual where method that should call orWhere. Note: That builder mock gets passed to the class below.

$builder = new LaravelBuilder($query);

Which then calls $builder->filter() which contains the below code.

$this->query->where(function ($query) use ($filterData) {
    /** @var \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query */
    foreach($filterData['columns'] as $colData) {
        /** @var \Samvaughton\Ldt\Column $column */
        $column = $colData['column'];

        // See if this column is searchable
        if (!$column->isSearchable() || !$colData['searchable']) continue;

        // If the individual column term is empty, use the main term
        $term = (empty($colData['term'])) ? $filterData['term'] : $colData['term'];

        // Actually apply the filter
        $query->orWhere($column->getSqlColumn(), "LIKE", "%{$term}%");
    }
});

The main part is the bottom bit $query->orWhere, the PHPUnit tests fail every time as it doesn't run the orWhere even once. Before you say that it might not reach the execution of it due to continue, the data I am passing will allow this.

I suspect this is due to how I am mocking the where method in the first place. If I include an exit before the foreach it does not execute indicating that it doesn't even run anything inside the callback. I know that is the default behaviour but how do I get Mockery to run the same/similar callback?

I have tried using partial mocks, using shouldExpect but don't fully understand it. I've also tried searching around but no luck with this scenario.

Would be great if I could learn how to use mockery within callbacks.

Wobble answered 20/12, 2013 at 10:37 Comment(0)
W
5

OK so I think this can be done a better way, but I resorted to partially mocking Laravel's query builder.

See line 324 in Illuminate\Database\Query\Builder

When using a closure Laravel spawns a new query builder which also needs to be mocked. Here's the code that worked:

$query = \Mockery::mock('Illuminate\Database\Query\Builder', function ($mock) {
    /** @var \Mockery\Mock $mock */
    $mock->makePartial();
    $mock->shouldReceive('where')->once()->passthru();
    $mock->shouldReceive('newQuery')->andReturn(
        \Mockery::mock('Illuminate\Database\Query\Builder', function ($mock) {
            /** @var \Mockery\Mock $mock */
            $mock->makePartial();
            $mock->shouldReceive('orWhere')->twice();
        })
    );
});

I needed to use makePartial() so the query builder retained its original functionality for closures (which is not true to unit testing) so this isn't the best solution. From here I mock the newQuery method which is called to spawn a new query builder instance which I then mock in a similar format.

Wobble answered 21/12, 2013 at 13:12 Comment(1)
This really helped me! I can't find anything in the Mockery docs about passing a closure as the second argument to mock. How did you know to do this?Ravishing

© 2022 - 2024 — McMap. All rights reserved.