mock(CouponCodePool::class, function (MockInterface $mock): void { $mock->shouldReceive('pickRandom')->once()->andReturn('1_AAAA'); }); $coupon = app(CouponService::class)->findOrCreateForPhone('61929248'); $this->assertSame('61929248', $coupon->phone); $this->assertSame('1_AAAA', $coupon->code); $this->assertDatabaseHas(Coupon::class, [ 'phone' => '61929248', 'code' => '1_AAAA', ]); } public function test_find_or_create_returns_existing_coupon_without_picking(): void { $existing = Coupon::factory()->create(['phone' => '61929248', 'code' => '2_BBBB']); $this->mock(CouponCodePool::class, function (MockInterface $mock): void { $mock->shouldNotReceive('pickRandom'); }); $coupon = app(CouponService::class)->findOrCreateForPhone('61929248'); $this->assertTrue($existing->is($coupon)); } public function test_find_or_create_throws_when_pool_exhausted(): void { $this->mock(CouponCodePool::class, function (MockInterface $mock): void { $mock->shouldReceive('pickRandom')->andReturn(null); }); $this->expectException(CouponPoolExhaustedException::class); app(CouponService::class)->findOrCreateForPhone('61929248'); } protected function tearDown(): void { Mockery::close(); parent::tearDown(); } }