'Laravel resource API does not load the updated version of relation data on update endpoint

I have Transaction model. In that model, there is a quantity field and a foodstuff relationship.

Then, inside foodstuff there is a stock field.

I was expecting that if I update the quantity in Transaction model, it should also update the stock in the foodstuff. The logic is written in the model and called inside the controller. Please read the commented codes, I put an explanation there to be clear.

TransactionType.php

<?php

namespace App\Enums;

use BenSampo\Enum\Enum;

/**
 * @method static static in()
 * @method static static out()
 * @method static static cancelled()
 * @method static static returned()
 */
final class TransactionType extends Enum
{
    const in = 'in';
    const out = 'out';
    const cancelled = 'cancelled';
    const returned = 'returned';
}

Transaction.php

<?php

namespace App\Models;

use App\Enums\TransactionType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Transaction extends Model
{
    use HasFactory;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'foodstuff_id',
        'type',
        'quantity',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'foodstuff_id' => 'int',
        'quantity' => 'int',
    ];

    /**
     * Update foodstuff stock based on the given transaction type.
     * 
     * @param  string  $type
     * @return void
     */
    public function updateStock(string $type): void
    {
        $currentStock = $this->foodstuff->stock;
        $quantity = $this->quantity;

        $type == TransactionType::in
            ? $this->foodstuff->update(['stock' => $currentStock + $quantity])
            : $this->foodstuff->update(['stock' => $currentStock - $quantity]);
    }

    /**
     * Get the foodstuff that owns the Transaction
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function foodstuff(): BelongsTo
    {
        return $this->belongsTo(Foodstuff::class);
    }
}

TransactionController.php

public function store(StoreTransactionRequest $request)
{
    $record = Transaction::create($request->validated());

    $record->updateStock($request->type);

    // This works fine, test passed
    return new TransactionResource($record->refresh());
}

public function update(UpdateTransactionRequest $request, Transaction $transaction)
{
    $oldStock = $transaction->foodstuff->stock;
    $transaction->update($request->validated());

    $transaction->updateStock($request->type);
    $newStock = $transaction->foodstuff->stock;

    // dd(
    //     $oldStock, // 47
    //     $newStock, // 48
    //
    //     // to confirm that the operation is working properly
    //     $request->type, // "in"
    //     $request->quantity, // 1
    //     $oldStock + $request->quantity, // 48
    //     $oldStock + $transaction->quantity, // 48
    // );

    // This one still using the old stock (47), eventhough I refreshed it.
    // Test fails,
    // Failed asserting that 47 matches expected 48.
    $transaction->foodstuff->refresh();
    return new TransactionResource($transaction->refresh());
}

TransactionResource.php

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class TransactionResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
     */
    public function toArray($request)
    {
        return [
            ...
            ...
            ...
            'foodstuff' => [
               'stock' => $this->foodstuff->stock,
                ...
                ...
            ],
        ];
    }
}

Feature/TransactionTest.php

// A test for the store endpoint
public function testWhenUserStoreNewRecordWithTypeOfInItShouldUpdateTheFoodstuffStock()
{
    Sanctum::actingAs(User::factory()->createOne());

    $foodstuff = Foodstuff::factory()->createOne();
    $record = Transaction::factory()
        ->state(['foodstuff_id' => $foodstuff->id, 'type' => TransactionType::in])
        ->makeOne()
        ->toArray();

    $response = $this->postJson(route('transactions.store'), $record);
    $response->assertCreated();
    $response->assertJsonFragment($record);

    $data = $response->decodeResponseJson();

    $computedStock = $foodstuff->stock + $record['quantity'];
    $this->assertEquals($computedStock, $data['data']['foodstuff']['stock']);
}

// A test for update endpoint
public function testWhenUserUpdateNewRecordWithTypeOfInItShouldUpdateTheFoodstuffStock()
{
    Sanctum::actingAs(User::factory()->createOne());

    $oldRecord = Transaction::factory()->createOne();
    $newRecord = Transaction::factory()
        ->state(['foodstuff_id' => $oldRecord->foodstuff_id, 'type' => TransactionType::in])
        ->makeOne()
        ->toArray();

    $response = $this->putJson(route('transactions.update', $oldRecord), $newRecord);
    $response->assertOk();
    $response->assertJsonFragment($newRecord);

    $data = $response->decodeResponseJson();

    $computedStock = $oldRecord->foodstuff->stock + $newRecord['quantity'];

    // dd(
    //     $oldRecord->foodstuff->stock, // 47
    //     $data['data']['foodstuff']['stock'], // 47
    //     $computedStock, // 48
    // );

    $this->assertEquals($computedStock, $data['data']['foodstuff']['stock']);
}

Test Result

   FAIL  Tests\Feature\TransactionTest
  ✓ when user store new record with type of in it should update the foodstuff stock
  ⨯ when user update new record with type of in it should update the foodstuff stock

  ---

  • Tests\Feature\TransactionTest > when user update new record with type of in it should update the foodstuff stock
  Failed asserting that 47 matches expected 48.

  at tests/Feature/TransactionTest.php:328
    324▕         //     $data['data']['foodstuff']['stock'],
    325▕         //     $computedStock,
    326▕         // );
    327▕ 
  ➜ 328▕         $this->assertEquals($computedStock, $data['data']['foodstuff']['stock']);
    329▕     }
    330▕ 
    331▕     public function testWhenUserUpdateNewRecordWithTypeOfOutItShouldUpdateTheFoodstuffStock()
    332▕     {
  

  Tests:  1 failed
  Time:   0.62s

Why is this happening and how can I make it load the updated version of foodstuff?



Solution 1:[1]

I tried to reproduce your code and they are all working fine except for your testing logic.

$this->assertEquals($computedStock, $data['data']['foodstuff']['stock']);


$data['data']['foodstuff']['stock'] // this one is already returning updated data.

// You're testing like
$this->assertEquals($updatedQuantity + 1, $updatedQuantity);

That's why your test failed not because of your code but because of your test logic.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 sithuaung