Ben
Ben

Reputation: 62326

Unit testing a closure in Laravel

The closure in the code below has made this code very difficult to test. How can I continue to eager load these items and maintain full testability?

public function scopeWithCompanyPreferences(Builder $builder)
{
    return $builder->with([
            'companies' => function ($query) {
                $query->with('companies');
                $query->with('preference_settings');
                $query->with('parent_company');
            }
        ]);
}

I've seen using Mockery the use of Mockery::on(), but I don't think that's useful given the array.

Upvotes: 5

Views: 2442

Answers (2)

Dmytro
Dmytro

Reputation: 113

However old this could be, I would like to expand the accepted answer.

I was having hard time writing a similar sort of test, and one thing that I've been missing was that $x['companies'] (as per this example) is a function that is called in a with. So if you need to test what's within that closure, you should write your expectations within a \Mockery::on(), and call the closure in the end, finally returning true.

So, having this code:

$builder->with([
    'companies' => function ($query) {
        $query->with('companies');
        $query->with('preference_settings');
        $query->with('parent_company');
    }
]);

A test would look like this:

$mockBuilder = \Mockery::mock(Builder::class);
$mockBuilder->shouldReceive('with')
    ->once()
    ->with(\Mockery::on(function ($relations) {
        $mockSubQuery = Mockery::mock(Builder::class);
        $mockSubQuery->shouldReceive('with')->once()->with('companies')->andReturnSelf();
        $mockSubQuery->shouldReceive('with')->once()->with('preference_settings')->andReturnSelf();
        $mockSubQuery->shouldReceive('with')->once()->with('parent_company')->andReturnSelf();
        $relations['companies']($mockSubQuery);
        return true;
    })
    ->andReturnSelf();

What I was trying was like this, which didn't work (as per the example):

$mockBuilder->shouldReceive('with')
    ->once()
    ->with(['companies' => \Mockery::on(function ($query) {
        $mockSubQuery = Mockery::mock(Builder::class);
        // assertions
        $query($mockSubQuery);
        return true;
    }])
    ->andReturnSelf();

Hope this helps.

Upvotes: 1

Kryten
Kryten

Reputation: 15740

If you're mocking the with method, you should be able to use Mockery::on() like this:

$b = \Mockery::mock("your_builder_class");
$b->shouldReceive("with")
    ->with(\Mockery::on(function($x){
            // test $x any way you like, for example...
            // ...a simple check to see if $x["companies"] is a function
            return is_callable($x["companies"]);
        }))
    ->once()
    ->andReturn("hello!");

Upvotes: 5

Related Questions