Skip to content

Testing

Nick Hamnett edited this page Sep 29, 2026 · 1 revision

Geolocator::fake() swaps the facade root for an in-memory fake, so lookups never touch a database:

use SameOldNick\Geolocator\Drivers\Fake\FakeGeolocator;
use SameOldNick\Geolocator\DTOs\AsnResult;
use SameOldNick\Geolocator\DTOs\LocationResult;
use SameOldNick\Geolocator\Facades\Geolocator;

/** @var FakeGeolocator $driver */
$driver = Geolocator::fake();

Geolocator::lookup('8.8.8.8'); // generated result, no database access

$result = new LocationResult(
    ipAddress: '8.8.8.8',
    country: null,
    city: null,
    asn: AsnResult::create(15169, 'Google LLC'),
);

// An exact address takes precedence over a glob, which takes precedence over the wildcard.
$driver->mock('8.8.8.8', $result); // one address
$driver->mock('8.8.8.*', $result); // any address matching the pattern
$driver->mock('1.1.1.1');          // null: this address resolves to nothing
$driver->mock('*', $result);       // anything else

mock() returns the fake so it can be chained, and the same results can be passed to fake() up front:

$driver = Geolocator::fake(
    mockedResults: [
        '8.8.8.8' => $result,
        '1.1.1.1' => null,
    ],
    chanceOfEmpty: 0,
);

A mocked value is a LocationResult, a closure, or null for an empty result. A closure receives the address and the name of the method that was called, so one mock can answer the edition-specific lookups differently:

$driver->mock('8.8.*', fn (string $ip, string $method) => $method === 'lookupAsn'
    ? new LocationResult(
        ipAddress: $ip,
        country: null,
        city: null,
        asn: AsnResult::create(15169, 'Google LLC'),
    )
    : null);

Addresses you have not mocked get generated data, except private, reserved and malformed addresses, which resolve to an empty result. A mock overrides that: a '*' wildcard answers private addresses too, so reach for it deliberately.

chanceOfEmpty is the percentage chance of an empty result for an unmocked public address. It defaults to 0, which keeps tests deterministic, and it is passed by name:

Geolocator::fake(chanceOfEmpty: 100);

The fake stays installed for the rest of the test, which is usually what you want since every test gets a fresh container. To hand the facade back to the configured driver mid-test, swap its real root back in:

Geolocator::swap(app(\SameOldNick\Geolocator\GeolocatorManager::class));

Faking a database reader

The fake above replaces the geolocator. To exercise the real ip-location-db driver instead, replace the reader it opens:

  • SameOldNick\Geolocator\Drivers\Fake\FakeReader implements the same Contracts\Reader interface as the real reader, returns generated records, and supports mock() with the same glob and wildcard matching rules as the fake geolocator.
  • The driver resolves each edition's reader from a Contracts\ReaderProvider. The shipped providers extend AbstractReaderProvider, so binding one of them to a subclass that returns a FakeReader is the seam to replace.

But a fake is not a fixture

A mocked lookup never opens a .mmdb file, so nothing in the suite proves the driver reads a real database correctly. Keep at least one lookup against a real database — in a staging environment, or in a test gated on the file being present.

Clone this wiki locally