Make changes to the database during a Livewire Pest chain

Make changes to the database during a Livewire Pest chain

Tests

Tests are great! They're probably the reason why you're still able to sleep at night after someone decided you had to make some last minute changes. Also, they really speed up your development. Imagine working on a piece of code that has a lot of dynamics based on variables, if we didn't have tests it would be really hard to work on it by having to manually click around in the browser in to check if the code still works after every change. We can do that in milliseconds with tests! Anyway, tests have made me a better and a more confident developer.

Pest

Pest is awesome! It makes the tests so much more readable. It allows you to still use PHPUnit and Pest syntax at the same time, so you can slightly shift over to Pest at your own pace. Pest makes your tests almost read like a nice story. That comes in handy when get back to a codebase after a few months or have to work on tests that someone else created.

Make changes in the background during the pest chain

Livewire Pest tests can look like a nice chain of actions and assertions on the Livewire component, which I fancy a lot. However, sometimes you need to make changes to the database or assert on a model (that is not on the Livewire component) during the chain at some point. There are a few ways how we can handle that.

I'll illustrate this with a very simple example. Imagine if we have an Order model in Laravel and an OrderComponent Livewire component. We have a refresh button that would call our refreshOrderCount() public method, and this would update the $orderCount public property. Simple stuff!

Here is the component:

<?php

namespace App\Http\Livewire;

use App\Models\Order;
use Livewire\Component;

class OrderDashboardComponent extends Component
{
    public int $orderCount = 0;

    public function mount()
    {
        $this->orderCount = Order::count();
    }

    public function render()
    {
        return view('livewire.order-dashboard-component');
    }

    public function refreshOrderCount()
    {
        $this->orderCount = Order::count();
    }
}

If we want to write a test for this behaviour, we would have to add a new order during the Pest Livewire test chain.

it('should update the orderCount', function(){
    livewire(OrderDashboardComponent::class)
        ->assertSet('orderCount', 0)
        ->call('refreshOrderCount')
                // Here we should add an order to the database
        ->assertSet('orderCount', 1);
}); // (This test would fail)

If we started with the test above, we should add the new order right after the ->assertSet('orderCount', 0) assertion. Unfortunately, we can't do this in this Pest chain. So we would have to do something like this:

it('should update the orderCount', function(){
    $component = livewire(OrderDashboardComponent::class);

    $component->assertSet('orderCount', 0);

    Order::factory()->create();

    $component->call('refreshOrderCount')
        ->assertSet('orderCount', 1);
});

The above code will work, but it looks kind of messy to me, so I came up with the idea to use tap() to make this work from within the chain.

it('should update the orderCount', function(){
    livewire(OrderDashboardComponent::class)
        ->assertSet('orderCount', 0)
        ->tap(function(){
            Order::factory()->create();
        })
        ->call('refreshOrderCount')
        ->assertSet('orderCount', 1);
});

Or even better:

it('should update the orderCount')
    ->livewire(OrderDashboardComponent::class)
    ->assertSet('orderCount', 0)
    ->tap(function(){
        Order::factory()->create();
    })
    ->call('refreshOrderCount')
    ->assertSet('orderCount', 1);

Great, this looks so much better to me! What do you think? You can also use tap to make assertions on a model that is not available in the Livewire component as a property.