bakedbean78's avatar

putJson results in no model instance being passed to the controller

Hello,

Somebody please help me out here.

I have a unit test for an api endpoint which is supposed to test the data structure of the json response. The issue is that the model id param which is being sent to the controller results in a null value for that model when I dd the model variable in the controller. I am using RefreshDatabase and WithoutMiddleware. The WithoutMiddleware is to forego the need for authorization. The request is an update PUT request. There are no global scopes being applied.

The route as outputted by php artisan route:list is PUT|PATCH api/venues/{venue}.

The relevant test code is as follows:

$response = $this
            ->actingAs($user)
            ->putJson('/api/venues/'.$venue->id, $data);

The relevant controller code is:

public function update(UpdateVenueRequest $request, Venue $venue)
 {
     $user = User::current();

     dd($venue);

The venue in question is created using a factory in the test. When I do Venue::find($venue->id) under where I have created the venue, the newly created model is successfully returned. So the problem seems to lie in the passing of the $venue->id to the controller. The $data variable is a simple array of values to be used in the update PUT request.

When I run dd($venue); at the top of the controller I get the following:

App\Models\Venue^ {#4853 // app/Http/Controllers/Venue/VenuesController.php:131
  #connection: null
  #table: null
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #with: []
  #withCount: []
  +preventsLazyLoading: false
  #perPage: 15
  +exists: false
  +wasRecentlyCreated: false
  #escapeWhenCastingToString: false
  #attributes: []
  #original: []
  #changes: []
  #casts: array:6 [
    "gallery" => "array"
    "rating" => "integer"
    "published" => "boolean"
    "feature_multiple_bookings" => "boolean"
    "last_outbound_comms_at" => "datetime"
    "private_artists_enabled" => "boolean"
  ]
  #classCastCache: []
  #attributeCastCache: []
  #dates: []
  #dateFormat: null
  #appends: []
  #dispatchesEvents: []
  #observables: []
  #relations: []
  #touches: []
  +timestamps: true
  #hidden: []
  #visible: []
  #fillable: array:13 [
    0 => "name"
    1 => "about"
    2 => "email"
    3 => "telephone"
    4 => "website"
    5 => "icon"
    6 => "gallery"
    7 => "rating"
    8 => "published"
    9 => "address_id"
    10 => "billing_address_id"
    11 => "last_outbound_comms_at"
    12 => "location_id"
  ]
  #guarded: array:1 [
    0 => "*"
  ]
  #automaticTax: false
  #customerIpAddress: null
  #estimationBillingAddress: []
  #collectTaxIds: false
  #couponId: null
  #promotionCodeId: null
  #allowPromotionCodes: false
  +mediaConversions: []
  +mediaCollections: []
  #deletePreservingMedia: false
  #unAttachedMediaLibraryItems: []
}

When I run dd($venue->id) it returns null. It appears to be getting an empty venue model instance even though there appears to have been a model created in the test.

Any ideas? I have no idea why this does not pass the model to the controller.

I have only included what I think is the relevant code. I can provide the complete test and/or controller if necessary.

Thanks.

0 likes
4 replies
LaryAI's avatar
Level 58

Based on the information provided, it seems that the issue lies in the way the $venue->id is being passed to the controller.

In the test code, you are using $venue->id directly in the URL string, but it seems that the $venue object is not being resolved correctly.

To fix this, you can try passing the $venue->id as a route parameter using the route() helper function. Here's an example:

$response = $this
    ->actingAs($user)
    ->putJson(route('venues.update', ['venue' => $venue->id]), $data);

Make sure that you have defined a named route for the update method in your routes file. It should look something like this:

Route::put('/api/venues/{venue}', [VenuesController::class, 'update'])->name('venues.update');

By using the route() helper function, Laravel will automatically resolve the correct URL for the given route name, including the venue parameter.

Give this a try and see if it resolves the issue. Let me know if you have any further questions!

bakedbean78's avatar

I've tried the above and I still have the same issue. So to recap, I replaced

        $response = $this
            ->actingAs($user)
            ->putJson('/api/venues/'.$venue->id, $data);

with:

        $response = $this
            ->actingAs($user)
            ->putJson(route('venues.update', ['venue' => $venue->id]), $data);

as advised.

Here is the route:

Route::apiResource('venues', VenuesController::class);

I have also tried it with the suggested route:

Route::put('/api/venues/{venue}', [VenuesController::class, 'update'])->name('venues.update');

Same.

Any ideas?

bakedbean78's avatar

There's no issues when making api alls through the app. Only within the test.

bakedbean78's avatar
bakedbean78
OP
Best Answer
Level 4

In case anybody ever reads this, I found the solution.

We are using auth0 on our site. We upgraded from auth0 ^6 to auth0 ^7. In order to get the tests to work with version 7 of the auth0/login package I had to remove 'auth0' from

->actingAs($user, 'auth0')

line in my test. I think this is because version 7 integrates with the laravel auth middleware directly rather than relying on a custom guard. But don't quote me on that. But removing 'auth0' from ->actingAs allows me to run the test. I had originally used the WithoutMiddleware trait in order to remove the auth middleware from the test. It did this successfully but it meant that ALL the middleware will be bypassed, meaning the route binding middleware along with all the others. Because the route binding middleware was being removed the Venue model was not being passed to the controller via the test. The solution was to remove the WithoutMiddleware trait from the test and add the ->withOutMiddleware method to the json call specifying auth:

$response = $this
            ->actingAs($user)
            ->withoutMiddleware([Authenticate::class])
            ->putJson(route('venues.update', $venue), $data);

And that is it .

1 like

Please or to participate in this conversation.