Skip to content

Scheduled Events Testing

Test Helpers

runSchedule(string $eventType)

Sends the scheduled event inline, bypassing the queue. Simulates what ProcessScheduledCommand does for a single instance.

php
ScheduledMachine::test()
    ->assertState('active')
    ->runSchedule('CHECK_EXPIRY')
    ->assertState('expired');

Throws AssertionFailedError if the schedule is not defined on the machine.

assertHasSchedule(string $eventType)

Asserts that the machine definition has a schedule for the given event type.

php
ScheduledMachine::test()
    ->assertHasSchedule('CHECK_EXPIRY')
    ->assertHasSchedule('DAILY_REPORT');

Key Patterns

What to TestHow
Schedule exists->assertHasSchedule('EVENT')
Event transitions machine->runSchedule('EVENT')->assertState('new_state')
Guards block transition->runSchedule('EVENT')->assertState('same_state')
Context updated by action->runSchedule('EVENT')->assertContext('key', value)
Nonexistent schedule->runSchedule('BAD') throws

Testing Resolvers

Resolvers are plain PHP classes — test them independently:

php
it('resolver returns expired applications', function (): void {
    // Seed test data
    Application::factory()->create([
        'created_at'      => now()->subDays(10),
        'status'          => ApplicationStatus::APPROVED,
        'application_mre' => 'mre-1',
    ]);

    $resolver = new ExpiredApplicationsResolver();
    $ids      = $resolver();

    expect($ids)->toContain('mre-1');
});

Testing with ProcessScheduledCommand

For integration testing, run the actual artisan command with Bus::fake():

php
use Illuminate\Support\Facades\Bus;

it('command dispatches batch for resolved instances', function (): void {
    Bus::fake();

    // Seed machine_current_states
    MachineCurrentState::insert([
        ['root_event_id' => 'mre-1', 'machine_class' => MyMachine::class,
         'state_id' => 'active', 'state_entered_at' => now()],
    ]);

    // Set up resolver
    MyResolver::setUp(['mre-1']);

    $this->artisan('machine:process-scheduled', [
        '--class' => MyMachine::class,
        '--event' => 'CHECK_EXPIRY',
    ])->assertSuccessful();

    Bus::assertBatched(fn ($batch) => $batch->jobs->count() === 1);
});

E2E Testing

For full pipeline verification without Bus::fake():

php
it('E2E: schedule pipeline transitions machine', function (): void {
    $machine = MyMachine::create();
    $machine->persist();
    $rootEventId = $machine->state->history->first()->root_event_id;

    MyResolver::setUp([$rootEventId]);

    $this->artisan('machine:process-scheduled', [
        '--class' => MyMachine::class,
        '--event' => 'CHECK_EXPIRY',
    ])->assertExitCode(0);

    $restored = MyMachine::create(state: $rootEventId);
    expect($restored->state->currentStateDefinition->id)->toBe('my_machine.expired');
});

Released under the MIT License.