Testovanie
Lifecycles::fake() vymení manažéra za fasádou aj v kontajneri, takže sa podvrhnú aj injektovaní manažéri, handle aj $model->transition():
use RoundlyConsulting\Lifecycle\Enums\DenialCode;
use RoundlyConsulting\Lifecycle\Facades\Lifecycles;
Lifecycles::fake()->denyNext('reopen', DenialCode::QuotaExceeded);
$this->post("/listings/{$listing->id}/reopen")->assertStatus(422);
Lifecycles::assertTransitionDenied($listing, 'reopen', DenialCode::QuotaExceeded);
Lifecycles::assertNotTransitioned($listing, 'reopen');- Nič nezapisuje a nič neodosiela — žiadne riadky, udalosti ani joby — no odmietne to, čo skutočný engine odmietne aj bez databázy, a pamätá si, o čo bol požiadaný.
- Prechody spúšťajú každú skutočnú kontrolu, ktorá nepotrebuje databázu ani Gate (neznámy prechod, nesprávny zdrojový stav, koncový stav, systémový kontext, typy aktérov a closures, dôvod, validácia payloadu), odmietnu neuloženú zmenu atribútu cyklu aj soft-deleted subjekt, zopakujú idempotentné kľúče a menia atribút modelu v pamäti.
- Zmrazenie: podvrhnuté freeze() odmieta ďalšie prechody (frozen), pokiaľ nemajú ignoresFreeze(), až do unfreeze() alebo jeho until. Obe vracajú to isté ako skutočné volania.
- Plány: schedule() spúšťa skutočné kontroly pri plánovaní okrem Gate (neznámy, koncový alebo nesprávny zdrojový prechod, systémový kontext, zmrazenie, pravidlá pre aktéra, dôvod, payload, povinný kľúč zo sensitive()). cancelScheduled() vráti true raz pre plán, ktorý fake vytvoril, a podvrhnutý prechod, ktorý opustí stav, na jeho plány zabudne.
- Expirácia: renew(), extend() a expireAt() vrátia okamih, ktorý by nastavilo skutočné volanie (pri renew() teraz + TTL stavu); stav bez expirácie vyhodí ExpiryException. Fake nezapisuje riadky expirácie, takže extend() pri modeli vytvorenom pod fakom predlžuje od teraz.
- Rollbacky vyberajú z vlastného zásobníka aplikovaných prechodov fakeu a zamietajú podľa skutočných pravidiel, ktoré nepotrebujú databázu (nič na vrátenie, nevratné či nekompenzujúce kroky, okná, zmrazenia, kroky len pre systém, dôvody), a plány fakeu vrátia tak ako skutočný rollback.
- Guardy, Gate abilities, kvóty, rate limity, termíny, limity, zapečatenia, očakávané verzie a ďalšie kontroly závislé od databázy sa preskočia. Model vytvorený pod fakom stále začína v počiatočnom stave a priamy zápis stále vyhodí výnimku. Čítania z handlu závislé od databázy (enteredAt(), isFrozen(), expiresAt(), history(), scheduled()) podvrhnuté zmeny nevidia — použite asercie.
Zamietanie na požiadanie
$fake = Lifecycles::fake();
$fake->denyNext('reopen', DenialCode::QuotaExceeded); // refuse once
$fake->deny('publish', 'no_photos', 'Add a photo first.'); // refuse every timeZamietnuté volanie vyhodí TransitionDeniedException (alebo zamietnutie vráti cez attempt()) presne ako skutočné zamietnutie, takže funguje aj toValidationException().
Asercie
Volajte ich na fasáde alebo na vrátenom fakeu. Voliteľné argumenty sú filtre; null zodpovedá čomukoľvek. Každá asercia o subjekte prijíma aj názov cyklu ako posledný argument — pre modely, ktorých cykly zdieľajú názvy prechodov. assertScheduled() a assertNothingScheduled() počítajú len prijaté plány a zamietnuté schedule() či rollback() nie je zamietnutím prechodu — nájdete ho v recorded():
Lifecycles::assertTransitioned($listing, 'reopen', fn (TransitionResult $result) => $result->record->reason === 'Back in stock');
Lifecycles::assertTransitionedTo($listing, ListingStatus::Active);
Lifecycles::assertTransitionDenied($listing, 'reopen', DenialCode::QuotaExceeded);
Lifecycles::assertRolledBack($listing);
Lifecycles::assertFrozen($listing);
Lifecycles::assertUnfrozen($listing);
Lifecycles::assertScheduled($listing, 'publish', $publishAt);
Lifecycles::assertScheduleCancelled($listing, 'publish');
Lifecycles::assertExpiryChanged($listing, ExpiryChange::Extend);
Lifecycles::assertAdopted($listing);
Lifecycles::assertSwept(1); // sweep() / schedules()->runDue()
Lifecycles::assertWarned(1); // schedules()->warn()
Lifecycles::assertScheduleRetried($scheduleId);
Lifecycles::assertPruned();
// Several lifecycles sharing transition names: name the one you mean
Lifecycles::assertTransitioned($order, 'cancel', lifecycle: 'payment_status');
// The negative counterparts
Lifecycles::assertNotTransitioned($listing, 'reopen');
Lifecycles::assertNothingTransitioned();
Lifecycles::assertNothingRolledBack();
Lifecycles::assertNothingFrozen();
Lifecycles::assertNothingUnfrozen();
Lifecycles::assertNothingScheduled();
Lifecycles::assertNothingCancelled();
Lifecycles::assertNoExpiryChanged();
Lifecycles::assertNothingAdopted();
Lifecycles::assertNotSwept();
Lifecycles::assertNotWarned();
Lifecycles::assertNothingRetried();
Lifecycles::assertNotPruned();
Lifecycles::recorded(); // list<RecordedCall>: method, request, result, deniedFeature test s fakeom
it('reopens a listing', function (): void {
Lifecycles::fake();
$listing = Listing::factory()->create(['status' => ListingStatus::Closed]);
$this->actingAs($owner)->post("/listings/{$listing->id}/reopen", ['reason' => 'Back in stock'])->assertRedirect();
Lifecycles::assertTransitioned($listing, 'reopen', fn (TransitionResult $result) => $result->record->reason === 'Back in stock');
expect($listing->fresh()->status)->toBe(ListingStatus::Closed); // the fake never writes
});Proti skutočnému enginu
Engine je dosť rýchly na feature testy nad SQLite. Stavy pripravte cez factories — deklarovaný nepočiatočný stav sa pri vytvorení prijme — a cez expirácie sa posúvajte pomocou Carbon::setTestNow() a Lifecycles::sweep():
use Illuminate\Support\Carbon;
use RoundlyConsulting\Lifecycle\Facades\Lifecycles;
it('expires an active listing after its TTL and grace period', function (): void {
$listing = Listing::factory()->create(['status' => ListingStatus::Active]);
Carbon::setTestNow(now()->addDays(34));
Lifecycles::sweep();
expect($listing->fresh()->status)->toBe(ListingStatus::Expired);
});Udalosti overte cez Event::fake([LifecycleTransitioned::class]) a históriu cez Lifecycles::for($model)->history() / lastTransition(). Zmena stavu uloženého modelu v afterCreating alebo v seederi vyžaduje Lifecycles::allowDirectWrites(). Ak test cyklus za behu predefinuje, zavolajte Lifecycles::definitions()->flush().
Prejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.