Summer Sale! All accounts are 50% off this week.

Sturm's avatar
Level 5

Testing with Resource relationships fails

When attempting to write a test for a modified version of one of Laravel Daily's courses, I am running into a relationship snag in the Resource.

First, the part of the test code in question:

        $user = User::factory()->create();
        $parkings = Parking::factory()
            ->count(5)
            ->for(Vehicle::factory())
            ->for(Shed::factory())
            ->sequence(
                [
                    'user_id' => $user->id,
                    'docked_at' => null,
                    'departed_at' => null,
                ],
                [
                    'user_id' => $user->id,
                    'docked_at' => now(),
                    'departed_at' => null,
                ],
                [
                    'user_id' => $user->id,
                    'docked_at' => now(),
                    'departed_at' => now(),
                ],
                [
                    'docked_at' => null,
                    'departed_at' => null,
                ],
                [
                    'docked_at' => now(),
                    'departed_at' => null,
                ],
            )
            ->create();

        $response = $this->actingAs($user)->getJson('/api/v1/parkings');

        $response->assertStatus(200);

There are more assertions, but for now, even just the first one fails.

Now, I also have a ParkingResource class which sets up the JSON to send back to the front end:

class ParkingResource extends JsonResource
{
    /**
     * @throws Exception
     */
    public function toArray(Request $request): array
    {
        $arrivalDate = $this->arrived_at;
        $dockedDate = $this->docked_at;
        $departedDate = $this->departed_at;

        if ($arrivalDate instanceof Carbon) {
            $arrivalDate = $this->arrived_at->format('Y-m-d H:i:s');
        }
        if ($dockedDate instanceof Carbon) {
            $dockedDate = $this->docked_at->format('Y-m-d H:i:s');
        }
        if ($departedDate instanceof Carbon) {
            $departedDate = $this->departed_at->format('Y-m-d H:i:s');
        }

        $waitDuration = $this->wait_duration ? CarbonInterval::seconds($this->wait_duration)->cascade()->forHumans() : null;
        $dockingDuration = $this->docking_duration ? CarbonInterval::seconds($this->docking_duration)->cascade()->forHumans() : null;

        return [
            'id' => $this->id,
            'shed' => [
                'name' => $this->shed->name,
                'average_waiting_duration' => $this->shed->average_waiting_duration,
                'average_docking_duration' => $this->shed->average_docking_duration,
            ],
            'vehicle' => [
                'plate_number' => $this->vehicle->plate_number,
                'description' => $this->vehicle->description,
            ],
            'arrived_at' => $arrivalDate,
            'docked_at' => $dockedDate,
            'departed_at' => $departedDate,
            'wait_duration' => $waitDuration,
            'docking_duration' => $dockingDuration,
        ];
    }
}

The test fails with a 500 status and the message: ErrorException: Attempt to read property "plate_number" on null in /opt/project/app/Http/Resources/ParkingResource.php:46

So clearly, it appears that the Vehicle isn't getting created or isn't being attached to the Parkings, or both.

Attempts to var_dump() the $parkings or $response variables result in no dumping at all, just the same error message.

What am I doing wrong?

0 likes
1 reply
Sturm's avatar
Level 5

Global Scope strikes again!

A colleague helped me out yesterday to discover why the Vehicle was not being retrieved. It is being created and attached to the Parking instance, but something kept preventing me from "seeing" it.

That something was my Global Scope on the model:

protected static function booted()
    {
        static::addGlobalScope('user', function (Builder $query) {
            $query->where('user_id', auth()->id());
        });
    }

Its purpose is to prevent anyone who does not own the vehicle from getting information about it. Or, put another way, it is to make sure the authenticated user can only retrieve their own vehicles. And since my test was creating a new Vehicle using a factory, which in turn creates a new user to tie to it, the $user that I was actingAs() in the test was not the owner of the Vehicle. Thus, the test couldn't retrieve it.

The fix was simple. Either remove the global scope or simply account for it when setting up the data for the test. I chose the latter (simplifying the count in the process):

        $user = User::factory()->create();
        $parkings = Parking::factory(5)
            ->for(Vehicle::factory()
                ->hasAttached($user)
            )
            ->for(Shed::factory())
            ->sequence(
[...]

Problem solved.

1 like

Please or to participate in this conversation.