The recording fake
Addresses::fake() swaps the manager — for the facade and for anything that injects AddressManager — with a recording fake that writes nothing, and returns it. Added addresses stay in memory, unsaved, and the owner’s reads (all(), ofType(), primary()) answer from the stored rows with the fake’s adds, updates, deletes and primary changes applied — so primary() returns what the real manager would after the same calls. Ownership is still enforced, and so is the refusal to promote a deleted address. Calls through the HasAddresses trait are recorded too:
use RoundlyConsulting\Addresses\DataTransferObjects\AddressData;
use RoundlyConsulting\Addresses\Facades\Addresses;
$fake = Addresses::fake();
$user->addAddress($data); // your code under test
Addresses::for($user)->setPrimary($address);
$fake->assertAdded($user, fn (AddressData $d) => $d->city === 'Bratislava');
$fake->assertPrimarySet($address);
$fake->assertNothingDeleted();| Assertion | Negative |
|---|---|
assertAdded(?Model $to = null, ?Closure $where = null) | assertNothingAdded() |
assertUpdated(?Address $address = null, ?Closure $where = null) | assertNothingUpdated() |
assertDeleted(?Address $address = null) | assertNothingDeleted() |
assertPrimarySet(?Address $address = null) | assertNothingPrimarySet() |
Every argument is optional. assertAdded() filters by owner and by a closure over the recorded AddressData; assertUpdated() takes the address and a closure over its AddressData.
Database assertions
For database-backed tests, the InteractsWithAddresses trait adds two assertions. In Pest, apply it with uses(); in PHPUnit, use it on the test class:
use RoundlyConsulting\Addresses\Enums\AddressType;
use RoundlyConsulting\Addresses\Testing\InteractsWithAddresses;
uses(InteractsWithAddresses::class);
$this->assertHasAddress($payer, ['city' => 'Bratislava']);
$this->assertPrimaryAddress($payer, AddressType::Office);use RoundlyConsulting\Addresses\Testing\InteractsWithAddresses;
use Tests\TestCase;
final class CustomerAddressTest extends TestCase
{
use InteractsWithAddresses;
}| Assertion | Passes when |
|---|---|
assertHasAddress(Model $addressable, array $criteria = []) | The model has an address matching every column => value pair; with no criteria, any address. |
assertPrimaryAddress(Model $addressable, AddressType $type) | The model has a primary address of that type. |
assertHasAddress() matches criteria against the addresses table columns, so compare stored values — ['type' => 'billing', 'country_iso' => 'SK'] — not enum cases. Soft-deleted addresses do not count.
The factory
Address::factory() generates realistic addresses. The default definition builds a plain address — is_primary false, so ask for a primary with the primary() state — of the default type, with a faker name, city, street and postcode and an alpha-3 country_iso:
| State | Effect |
|---|---|
primary() | Sets is_primary to true. |
ofType(AddressType $type) | Sets type to the enum’s value. |
billing() | Shortcut for ofType(AddressType::Billing). |
shipping() | Shortcut for ofType(AddressType::Shipping). |
use RoundlyConsulting\Addresses\Address;
use RoundlyConsulting\Addresses\Enums\AddressType;
// through the owner's relation — sets addressable_type and addressable_id for you
$customer->addresses()->save(Address::factory()->billing()->primary()->make());
// standalone, with the owner set explicitly
Address::factory()->shipping()->create([
'addressable_type' => $customer->getMorphClass(),
'addressable_id' => $customer->getKey(),
]);
Address::factory()->ofType(AddressType::Office)->make();The factory writes rows directly — no country normalisation, no AddressCreated, no sibling demotion — and always builds the packaged Address class. On PostgreSQL and SQLite the partial unique index still refuses a second live primary in one owner + type group.
A complete test
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Addresses\Enums\AddressType;
use RoundlyConsulting\Addresses\Events\PrimaryAddressChanged;
use RoundlyConsulting\Addresses\Testing\InteractsWithAddresses;
uses(InteractsWithAddresses::class);
it('promotes a saved shipping address', function () {
Event::fake([PrimaryAddressChanged::class]);
$customer = Customer::factory()->create();
$address = $customer->newAddress()
->type(AddressType::Shipping)
->in('Bratislava')->at('Somewhere 1')->postalCode('81101')->country('sk')
->save();
$customer->setPrimaryAddress($address);
$this->assertHasAddress($customer, ['country_iso' => 'SK']);
$this->assertPrimaryAddress($customer, AddressType::Shipping);
Event::assertDispatched(PrimaryAddressChanged::class);
});Run it with your usual database setup (for example RefreshDatabase) and the package migration published. To run the package’s own suite from its repository:
composer testShow your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.