From 61863e0bd509575de595f4b782ede0339d751b09 Mon Sep 17 00:00:00 2001 From: Joel Male Date: Wed, 27 May 2026 21:31:54 +1000 Subject: [PATCH] feat: add Cart::fake(), assertion methods, and CartFactory Adds Cart::fake() which swaps the active driver to NullDriver and returns a CartFactory fluent test builder. Adds assertion methods (assertContains, assertCount, assertTotalQuantity, assertSubTotal, assertTotal, assertConditionApplied, assertEmpty, assertNotEmpty) to Cart for use in tests. CartFactory provides withItems(), withCondition(), and withCoupon() as chainable helpers for seeding cart state. --- src/Cart.php | 78 +++++++++++++++++ src/Testing/CartFactory.php | 37 ++++++++ tests/Unit/Testing/CartFactoryTest.php | 60 +++++++++++++ tests/Unit/Testing/FakeCartTest.php | 112 +++++++++++++++++++++++++ 4 files changed, 287 insertions(+) create mode 100644 src/Testing/CartFactory.php create mode 100644 tests/Unit/Testing/CartFactoryTest.php create mode 100644 tests/Unit/Testing/FakeCartTest.php diff --git a/src/Cart.php b/src/Cart.php index 73019ce..b6b09e8 100644 --- a/src/Cart.php +++ b/src/Cart.php @@ -5,10 +5,13 @@ namespace Wearepixel\Cart; use Closure; +use PHPUnit\Framework\Assert; use Wearepixel\Cart\Coupons\Coupon; +use Wearepixel\Cart\Drivers\NullDriver; use Wearepixel\Cart\Helpers\Helpers; use Wearepixel\Cart\Shipping\ShippingRate; use Wearepixel\Cart\Tax\TaxRule; +use Wearepixel\Cart\Testing\CartFactory; use Wearepixel\Cart\Validators\CartItemValidator; use Wearepixel\Cart\Exceptions\InvalidItemException; use Wearepixel\Cart\Exceptions\UnknownModelException; @@ -49,6 +52,81 @@ public function getDriver(): CartDriver return $this->driver; } + public function fake(): CartFactory + { + $this->driver = new NullDriver($this->driver->getSessionKey()); + + return new CartFactory($this); + } + + public function assertContains(int|string $id): void + { + Assert::assertTrue( + $this->has($id), + "Failed asserting that cart contains item [{$id}]." + ); + } + + public function assertCount(int $count): void + { + Assert::assertCount( + $count, + $this->getContent(), + "Failed asserting that cart has [{$count}] items." + ); + } + + public function assertTotalQuantity(int|float $quantity): void + { + Assert::assertEquals( + $quantity, + $this->getTotalQuantity(), + "Failed asserting that cart total quantity is [{$quantity}]." + ); + } + + public function assertSubTotal(float $amount): void + { + Assert::assertEquals( + $amount, + $this->getSubTotal(false), + "Failed asserting that cart subtotal is [{$amount}]." + ); + } + + public function assertTotal(float $amount): void + { + Assert::assertEquals( + $amount, + $this->getTotal(), + "Failed asserting that cart total is [{$amount}]." + ); + } + + public function assertConditionApplied(string $conditionName): void + { + Assert::assertNotNull( + $this->getCondition($conditionName), + "Failed asserting that condition [{$conditionName}] is applied to the cart." + ); + } + + public function assertEmpty(): void + { + Assert::assertTrue( + $this->isEmpty(), + 'Failed asserting that the cart is empty.' + ); + } + + public function assertNotEmpty(): void + { + Assert::assertFalse( + $this->isEmpty(), + 'Failed asserting that the cart is not empty.' + ); + } + public function getSessionKey(): string { return $this->driver->getSessionKey(); diff --git a/src/Testing/CartFactory.php b/src/Testing/CartFactory.php new file mode 100644 index 0000000..9de5429 --- /dev/null +++ b/src/Testing/CartFactory.php @@ -0,0 +1,37 @@ +cart->add($i, "Test Item {$i}", 10.00, 1); + } + + return $this; + } + + public function withCondition(CartCondition $condition): static + { + $this->cart->condition($condition); + + return $this; + } + + public function withCoupon(Coupon $coupon): static + { + $this->cart->coupon($coupon); + + return $this; + } +} diff --git a/tests/Unit/Testing/CartFactoryTest.php b/tests/Unit/Testing/CartFactoryTest.php new file mode 100644 index 0000000..7127cb5 --- /dev/null +++ b/tests/Unit/Testing/CartFactoryTest.php @@ -0,0 +1,60 @@ +shouldReceive('dispatch'); + + return new Cart( + new SessionDriver(new SessionMock, 'TESTKEY'), + $events, + 'cart', + require __DIR__ . '/../../Helpers/ConfigMock.php', + ); +} + +beforeEach(function () { + $this->cart = makeCartForFactory(); + $this->factory = new CartFactory($this->cart); +}); + +afterEach(fn() => Mockery::close()); + +describe('CartFactory', function () { + test('withItems adds N generic items to the cart', function () { + $this->factory->withItems(3); + + expect($this->cart->getContent()->count())->toBe(3); + expect($this->cart->getTotalQuantity())->toBe(3); + }); + + test('withCondition adds a condition to the cart', function () { + $condition = new CartCondition(['name' => 'GST', 'type' => 'tax', 'value' => '10%', 'target' => 'subtotal']); + $this->factory->withCondition($condition); + + expect($this->cart->getCondition('GST'))->not->toBeNull(); + }); + + test('methods are chainable', function () { + $result = $this->factory->withItems(2)->withCondition( + new CartCondition(['name' => 'Discount', 'type' => 'discount', 'value' => '-5%', 'target' => 'subtotal']) + ); + + expect($result)->toBeInstanceOf(CartFactory::class); + expect($this->cart->getContent()->count())->toBe(2); + expect($this->cart->getCondition('Discount'))->not->toBeNull(); + }); + + test('withItems generates unique item IDs', function () { + $this->factory->withItems(3); + $ids = $this->cart->getContent()->keys()->toArray(); + + expect(array_unique($ids))->toHaveCount(3); + }); +}); diff --git a/tests/Unit/Testing/FakeCartTest.php b/tests/Unit/Testing/FakeCartTest.php new file mode 100644 index 0000000..58073ef --- /dev/null +++ b/tests/Unit/Testing/FakeCartTest.php @@ -0,0 +1,112 @@ +shouldReceive('dispatch'); + + return new Cart( + new SessionDriver(new SessionMock, 'TESTKEY'), + $events, + 'cart', + require __DIR__ . '/../../Helpers/ConfigMock.php', + ); +} + +beforeEach(function () { + $this->cart = makeFakeableCart(); +}); + +afterEach(fn() => Mockery::close()); + +describe('Cart::fake()', function () { + test('returns the CartFactory instance', function () { + expect($this->cart->fake())->toBeInstanceOf(\Wearepixel\Cart\Testing\CartFactory::class); + }); + + test('swaps driver to NullDriver', function () { + $this->cart->fake(); + expect($this->cart->getDriver()->getDriverName())->toBe('null'); + }); + + test('Cart operations work after fake()', function () { + $this->cart->fake(); + $this->cart->add(1, 'Widget', 50.00, 2); + + expect($this->cart->isEmpty())->toBeFalse(); + expect($this->cart->getContent()->count())->toBe(1); + }); + + test('assertContains passes when item is in cart', function () { + $this->cart->fake(); + $this->cart->add(1, 'Widget', 50.00, 1); + + expect(fn() => $this->cart->assertContains(1))->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertContains fails when item is not in cart', function () { + $this->cart->fake(); + + expect(fn() => $this->cart->assertContains(99))->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertCount passes with correct count', function () { + $this->cart->fake(); + $this->cart->add(1, 'A', 10.00, 1); + $this->cart->add(2, 'B', 20.00, 1); + + expect(fn() => $this->cart->assertCount(2))->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertTotalQuantity passes with correct quantity', function () { + $this->cart->fake(); + $this->cart->add(1, 'A', 10.00, 3); + + expect(fn() => $this->cart->assertTotalQuantity(3))->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertSubTotal passes with correct subtotal', function () { + $this->cart->fake(); + $this->cart->add(1, 'Widget', 50.00, 2); + + expect(fn() => $this->cart->assertSubTotal(100.00))->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertTotal passes with correct total', function () { + $this->cart->fake(); + $this->cart->add(1, 'Widget', 50.00, 1); + + expect(fn() => $this->cart->assertTotal(50.00))->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertConditionApplied passes when condition exists', function () { + $this->cart->fake(); + $this->cart->condition(new CartCondition(['name' => 'GST', 'type' => 'tax', 'value' => '10%', 'target' => 'subtotal'])); + + expect(fn() => $this->cart->assertConditionApplied('GST'))->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertConditionApplied fails when condition does not exist', function () { + $this->cart->fake(); + + expect(fn() => $this->cart->assertConditionApplied('GST'))->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertEmpty passes when cart is empty', function () { + $this->cart->fake(); + + expect(fn() => $this->cart->assertEmpty())->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); + + test('assertNotEmpty passes when cart has items', function () { + $this->cart->fake(); + $this->cart->add(1, 'Widget', 10.00, 1); + + expect(fn() => $this->cart->assertNotEmpty())->not->toThrow(\PHPUnit\Framework\AssertionFailedError::class); + }); +});