PW_Parsons
PW_Parsons

Reputation: 1271

How to bind a model to a request in unit tests

I am struggling to bind a model to the request in a unit test so that the relationship of the model can be retrieved in the form request.

This is the form request:

class TimeSlotUpdateRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'time' => [
                'required', 'string', 'max:50',
                Rule::unique('time_slots')
                    ->where('schedule_id', $this->timeSlot->schedule->id)
                    ->ignore($this->timeSlot),
            ],
        ];
    }
}

This is the test (The assertExactValidationRules comes from Jason McCreary's Laravel test assertions package):

/** @test **/
public function it_verifies_the_validation_rules(): void
{
    $timeSlot = TimeSlot::factory()->create();

    $request = TimeSlotUpdateRequest::create(
        route('admin.timeSlots.update', $timeSlot),
        'PATCH'
    )
        ->setContainer($this->app);

    $request->setRouteResolver(function () use ($request) {
        return Route::getRoutes()->match($request);
    });

    $this->assertExactValidationRules([
        'time' => [
            'required', 'string', 'max:50',
            Rule::unique('time_slots')
                ->where('schedule_id', $timeSlot->schedule->id)
                ->ignore($timeSlot->id),
        ],
    ], $request->rules());
}

The test passes when the where clause is removed from the test and form request but fails with the where clause with the error ErrorException: Trying to get property 'schedule' of non-object

I tried to step through a request with xDebug but still don't understand how to route model binding is done.

How can the $timeSlot model be bound request or route so that the schedule relationship is accessible in the form request?

Any help will be appreciated.

Upvotes: 1

Views: 1183

Answers (2)

N.L.Menke
N.L.Menke

Reputation: 11

This is an old question, but I got here with the same issue. For anyone confused by the accepted answer, here are the suggested changes:

in the FormRequest:

...
Rule::unique('time_slots')
    ->where('schedule_id', $this->route('timeSlot')->schedule->id)
    ->ignore($this->route('timeSlot')),
...

and in the test:

...
$request->setRouteResolver(function () use ($request, $timeSlot) {
    $route = Route::getRoutes()->match($request);
    $route->setParameter('timeSlot', $timeSlot);

    return $route;
});
...

Upvotes: 1

lagbox
lagbox

Reputation: 50531

The Route Model Binding is handled via a middleware, the SubstituteBindings middleware. So the request would have to pass through the middleware stack. Since you are not doing that I suppose you could set the parameter on the route yourself:

$route->setParameter($name, $value);

$route would be the route object returned from match.

Also when dealing with a Request, if you want a route parameter you should be explicit about it and not use the dynamic property as it will return an input before it falls back to returning a route parameter:

$this->route('timeSlot');

Upvotes: 2

Related Questions