NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky

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();
AsserciaPrejde, 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 test

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 kryptomien

Odoslaní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.