Testovanie
Coupons::fake() nahradí manažéra — pre fasádu aj pre každý injektovaný CouponManager — triedou CouponsFake, záznamníkom, ktorý nič nezapisuje: žiadne riadky ani udalosti. Vráti ho pre asercie. Cez manažéra idú aj model Coupon, trait HasCoupons, pravidlo Redeemable a oba Artisan príkazy, takže sa zaznamenajú aj uplatnenia cez $coupon->redeemBy() a $user->redeemCoupon():
use RoundlyConsulting\Coupons\Enums\DiscountType;
use RoundlyConsulting\Coupons\Facades\Coupons;
use RoundlyConsulting\Money\Money;
$fake = Coupons::fake();
Coupons::generate(DiscountType::Fixed, 500, 'TEN', currency: 'USD');
$result = Coupons::code('TEN')->redeem(Money::ofMinor(5000, 'USD'), $user);
$result->total; // 45.00 USD — the real discount math, no row written
$user->redeemCoupon('WELCOME', Money::ofMinor(2000, 'USD')); // recorded too
$fake->assertCreated(fn ($coupon) => $coupon->code === 'TEN');
$fake->assertRedeemed('TEN', fn ($result) => $result->redeemer?->is($user));
$fake->assertRedeemed('WELCOME');
$fake->assertNothingRevoked();Všetky asercie
$fake->assertCreated(); // or a callback: fn (Coupon $c) => …
$fake->assertNothingCreated();
$fake->assertRedeemed('SUMMER'); // by code
$fake->assertRedeemed('SUMMER', fn ($result) => /* ... */ true); // by code + callback
$fake->assertRedeemed(callback: fn ($result) => /* ... */ true); // by callback only
$fake->assertNotRedeemed('OTHER');
$fake->assertNothingRedeemed();
$fake->assertRedemptionFailed('OLD', RedemptionFailureReason::Expired); // reason optional; a string works too
$fake->assertRevoked('SUMMER'); // code optional
$fake->assertExpiredAll();
$fake->assertNothingRevoked(); // no revoke() and no expireAll()
$fake->assertPruned(days: 30, force: false); // both optional
$fake->assertNothingPruned();| Assercia | Prejde, keď |
|---|---|
assertCreated(?callable $callback = null) | Vznikol akýkoľvek kupón — alebo taký, ktorý callback prijme. |
assertNothingCreated() | Nevznikol žiadny kupón. |
assertRedeemed(?string $code = null, ?callable $callback = null) | Akékoľvek úspešné uplatnenie alebo také, ktoré vyhovie kódu a/alebo callbacku. Len callback: assertRedeemed(callback: fn ($result) => …). |
assertNotRedeemed(string $code) | Žiadne úspešné uplatnenie daného kódu. |
assertNothingRedeemed() | Žiadne úspešné uplatnenia. |
assertRedemptionFailed(string $code, RedemptionFailureReason|string|null $reason = null) | Odmietnutý pokus pre daný kód — voliteľne s daným dôvodom ako enum alebo jeho hodnota. |
assertRevoked(?string $code = null) | Odvolaný bol akýkoľvek kupón — alebo ten s daným kódom — cez revoke() či code()->revoke(). |
assertExpiredAll() | Zavolalo sa expireAll(). |
assertNothingRevoked() | Nezavolalo sa revoke() ani expireAll(). |
assertPruned(?int $days = null, ?bool $force = null) | Zavolalo sa prune() — voliteľne s daným oknom a/alebo režimom. |
assertNothingPruned() | Nezavolalo sa prune(). |
Ako sa fake správa
- generate(), create() a createQuietly() vracajú neuložené modely Coupon — s kódom FAKE-1, FAKE-2, …, ak kód nezadáte —, zaznamenajú ich a nespúšťajú žiadne udalosti. Odmietnu rovnaké neplatné definície ako skutočné metódy (InvalidCouponDefinition), no neoverujú, či je kód obsadený.
- Čítanie najprv prehľadá kupóny vytvorené na fake, potom databázu: find(), findOrFail() a exists(). Kódy sa porovnávajú bez ohľadu na veľkosť písmen, rovnako ako v produkcii. redeemable() vracia vždy prázdny builder.
- Neznámy kód sa správa ako nový kupón bez obmedzení s nulovou hodnotou: check() vráti null, preview() nulu a redeem() zaznamená úspech — tok „overiť a uplatniť“ tak funguje bez prípravy dát.
- Kupóny vytvorené na fake sa tiež považujú za aktívne. Riadok v databáze prejde skutočnými kontrolami: pripravený kupón, ktorý nikto neaktivoval, sa odmietne ako Expired, presne ako v produkcii.
- redeem() nikdy nevyhodí výnimku. Úspech vráti zľavu a celkovú sumu, ktoré by vypočítalo skutočné uplatnenie — 20 % kupón na 50.00 EUR dá discount 10.00 a total 40.00; odmietnutie vráti nulovú zľavu a plnú cenu, zaznamená sa len ako neúspech a nikdy sa neráta ako uplatnenie.
- revoke() ukončí platnosť kupónu len v pamäti, bez uloženia, a zaznamená ho; expireAll() zaznamená volanie a ukončí platnosť zodpovedajúcich platných kupónov vytvorených na fake; prune() zaznamená svoje argumenty a vráti 0.
Ak chcete otestovať odmietnutie, použite kupón, ktorý by bol odmietnutý — vytvorte ho na fake cez Coupons::generate(), ako riadok v databáze, alebo odovzdajte model ako tu:
use RoundlyConsulting\Coupons\Enums\RedemptionFailureReason;
use RoundlyConsulting\Coupons\Facades\Coupons;
use RoundlyConsulting\Coupons\Models\Coupon;
use RoundlyConsulting\Money\Money;
$fake = Coupons::fake();
$expired = Coupon::factory()->percentage(1000)->expired()->make(['code' => 'OLD']);
Coupons::redeem($expired, Money::ofMinor(1000, 'EUR')); // recorded as a failure, never throws
$fake->assertRedemptionFailed('OLD', RedemptionFailureReason::Expired); // or 'expired'
$fake->assertNotRedeemed('OLD');Artisan príkazy tiež injektujú manažéra, takže ich fake zaznamená:
use RoundlyConsulting\Coupons\Facades\Coupons;
$fake = Coupons::fake();
$this->artisan('coupons:expire', ['--code' => 'SUMMER']);
$this->artisan('coupons:prune', ['--days' => 90, '--force' => true]);
$fake->assertExpiredAll();
$fake->assertPruned(days: 90, force: true);Factory
Coupon::factory() obsahuje stavy pre každý typ kupónu; k dispozícii je aj CouponRedemption::factory():
use RoundlyConsulting\Coupons\Models\Coupon;
use RoundlyConsulting\Money\Money;
Coupon::factory()->active()->fixed(500, 'EUR')->withMinimumSpend(Money::ofMinor(2000, 'EUR'))->create();
Coupon::factory()->active()->percentage(2500)->create(['code' => 'QUARTER']);
Coupon::factory()->active()->cappedPercentage(5000, Money::ofMinor(300, 'EUR'))->create();
Coupon::factory()->freeShipping()->expired()->create();- active() / expired() — aktivovaný alebo expirovaný pred hodinou.
- fixed(int $value = 100, ?string $currency = null) — uzamknutý na menu, predvolene coupons.default_currency.
- percentage(int $basisPoints = 1000) — neuzamknutý.
- freeShipping().
- cappedPercentage(int $basisPoints = 2500, ?Money $maxDiscount = null) — strop je predvolene 500 najmenších jednotiek predvolenej meny.
- withMinimumSpend(Money $minimumSpend) a forCurrency(string $currency) — oba uzamknú menu.
Proti skutočnej databáze
Pri end-to-end testoch publikujte migrácie do aplikácie, aby RefreshDatabase vytvoril obe tabuľky, a potom testujte cez skutočný tok:
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Coupons\Events\CouponExhausted;
use RoundlyConsulting\Coupons\Exceptions\CouponAtMaxUsage;
use RoundlyConsulting\Coupons\Facades\Coupons;
use RoundlyConsulting\Coupons\Models\Coupon;
use RoundlyConsulting\Money\Money;
it('sells out after the last use', function () {
Event::fake([CouponExhausted::class]);
Coupon::factory()->active()->fixed(500, 'EUR')->create(['code' => 'ONCE', 'max_usage' => 1]);
$result = Coupons::redeem('ONCE', Money::ofMinor(5000, 'EUR'));
expect($result->total->minor())->toBe('4500');
expect(fn () => Coupons::redeem('ONCE', Money::ofMinor(5000, 'EUR')))
->toThrow(CouponAtMaxUsage::class);
Event::assertDispatchedTimes(CouponExhausted::class, 1);
});Vlastné testy balíka spustíte príkazom:
composer testPrejavte 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.