---
title: "Testing — Google Places for Laravel | Roundly"
description: "GooglePlaces::fake() records every call and answers from seeded results — assert what your code asked Google, with no paid API traffic."
url: https://roundly-consulting.com/open-source/docs/google-places-for-laravel/testing
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Google Places for Laravel](https://roundly-consulting.com/open-source/docs/google-places-for-laravel.md)

# Testing

GooglePlaces::fake() swaps the live client for a recording GooglePlacesFake and returns it — no Http::fake(), no live (paid) calls. Nothing reaches Google: every call is recorded and answered from what the with\*() seeders queued:

```php
use RoundlyConsulting\GooglePlaces\DataTransferObjects\AutocompletePrediction;
use RoundlyConsulting\GooglePlaces\DataTransferObjects\AutocompleteQuery;
use RoundlyConsulting\GooglePlaces\DataTransferObjects\Place;
use RoundlyConsulting\GooglePlaces\Facades\GooglePlaces;

$fake = GooglePlaces::fake();

$fake->withAutocomplete([new AutocompletePrediction('Café Roma', 'p1', ['cafe'])]);
$fake->withDetails(new Place('Café Roma'));

// ...exercise code that calls the facade...

$fake->assertAutocompleted(fn (AutocompleteQuery $q) => $q->input === 'Café');
$fake->assertDetailsRequested('p1');
$fake->assertNothingGeocoded();
```

The fake is a PlacesClient installed behind the facade and the container binding, so constructor-injected clients, session(), matrix(), photo() (including save(), which writes the seeded bytes), the paginated search helpers, the google-places:check command and the google\_places geolocation driver all hit it.

## Seeding results

| Seeder | Feeds | When not seeded |
| --- | --- | --- |
| `withAutocomplete(array $predictions)` | `autocomplete()` | Empty collection. |
| `withDetails(?Place $place)` | `details()` | null. |
| `withGeocode(array $results)` | `geocode()` | Empty collection. |
| `withGeocodeAddress(array $results)` | `geocodeAddress()` | Empty collection. |
| `withTextSearch(array $places)` | `textSearch(), textSearchPaginated()` | Empty; the paginator returns one page. |
| `withNearbySearch(array $places)` | `nearbySearch(), nearbySearchPaginated()` | Empty; the paginator returns one page. |
| `withFindPlace(?Place $place)` | `findPlace()` | null. |
| `withDistance(Distance\|MultipleDistances $distance)` | `distance()` | A zero Distance. |
| `withMatrix(DistanceMatrix $matrix)` | `computeMatrix(), matrix()->…()` | An empty matrix sized to the query. |
| `withPhoto(string $bytes, ?string $uri = null)` | `photoContents(), photoUri(), photo()->…` | Empty bytes; a fake key-free URL built from the photo name. |
| `withPhotoUrl(string $url)` | `photoUrl()` | A media URL built from the photo name. |
| `withCheck(array $results)` | `check(), google-places:check` | Three healthy results. |

## Assertions

Each call is recorded as its query object — scalars are normalised to the query class first. Without a callback an assertion passes if the call happened at all; with one, at least one recorded query must match:

| Assertion | Passes when |
| --- | --- |
| `assertAutocompleted(fn (AutocompleteQuery $q) => $q->input === 'Café')` | An autocomplete call (matching the callback). |
| `assertDetailsRequested('p1')` | A details call; a string matches the place id, a callback gets the DetailsQuery. |
| `assertGeocoded(fn (ReverseGeocodingQuery $q) => $q->location->latitude === 48.1486)` | A reverse geocode. |
| `assertAddressGeocoded('Hlavné námestie 1, Bratislava')` | A forward geocode; a string matches the address. |
| `assertTextSearched(fn (TextSearchQuery $q) => $q->textQuery === 'pizza')` | A text search, paginated included. |
| `assertNearbySearched(fn (NearbySearchQuery $q) => $q->radius === 1500)` | A nearby search, paginated included. |
| `assertFindPlaceRequested(fn (string $text) => $text === 'Eiffel Tower')` | A findPlace() — the callback gets the search text only, not the bias. |
| `assertDistanceRequested(fn (DistanceQuery $q) => $q->type === TravelMode::Driving)` | A distance() call. |
| `assertMatrixComputed(fn (MatrixQuery $q) => count($q->origins) === 2)` | A matrix, via computeMatrix() or matrix()->…(). |
| `assertPhotoRequested('places/p1/photos/a')` | A photo’s bytes or key-free URL; a callback gets the PhotoQuery (contents is false for a URL). |
| `assertChecked()` | check() ran — directly or through google-places:check. |
| `assertNothingAutocompleted()` | No autocomplete call. |
| `assertNothingGeocoded()` | No reverse-geocode call. |
| `assertNothingRequested()` | No call at all, except photoUrl() and building the handles. |

session(), matrix() and photo() themselves are not recorded — the calls the handles make are. Session calls land as ordinary autocomplete and details calls with the session token set on the query, so assert on $q->sessionToken. A fresh call is recorded with its flag, so assert on $q->fresh — assertDetailsRequested(fn (DetailsQuery $q) => $q->fresh), or assertMatrixComputed(fn (MatrixQuery $q) => $q->fresh) for matrix()->fresh(). The fake never throws.

## Building fake results

Results are plain DTOs, so you can construct them directly:

```php
use RoundlyConsulting\GooglePlaces\DataTransferObjects\Distance;
use RoundlyConsulting\GooglePlaces\DataTransferObjects\DistanceQuery;
use RoundlyConsulting\GooglePlaces\DataTransferObjects\Geometry;
use RoundlyConsulting\GooglePlaces\DataTransferObjects\Location;
use RoundlyConsulting\GooglePlaces\DataTransferObjects\ReverseGeocodingResult;
use RoundlyConsulting\GooglePlaces\Enums\TravelMode;
use RoundlyConsulting\GooglePlaces\Facades\GooglePlaces;

it('quotes a delivery time for a typed address', function () {
    $fake = GooglePlaces::fake();

    $point = new Location(48.1435, 17.1089);

    $fake->withGeocodeAddress([
        new ReverseGeocodingResult(
            address: 'Hlavné námestie 1, 811 01 Bratislava, Slovakia',
            placeId: 'p1',
            geometry: new Geometry($point, $point, $point),
            types: ['street_address'],
        ),
    ]);
    $fake->withDistance(new Distance('4.2 km', 4200, '12m', 720, TravelMode::Driving));

    // ...call the code under test...

    $fake->assertAddressGeocoded('Hlavné námestie 1, Bratislava');
    $fake->assertDistanceRequested(fn (DistanceQuery $q) => $q->type === TravelMode::Driving);
    $fake->assertNothingAutocompleted();
});
```

## Guarding against calls

```php
use RoundlyConsulting\GooglePlaces\DataTransferObjects\Place;
use RoundlyConsulting\GooglePlaces\DataTransferObjects\TextSearchQuery;
use RoundlyConsulting\GooglePlaces\Facades\GooglePlaces;

it('renders the cached page without calling Google', function () {
    $fake = GooglePlaces::fake();

    $this->get('/places/cached')->assertOk();

    $fake->assertNothingRequested();   // no lookup, photo request or check at all
});

it('searches by text and never geocodes', function () {
    $fake = GooglePlaces::fake()->withTextSearch([new Place('Pizza Roma')]);

    $this->get('/search?q=pizza')->assertOk();

    $fake->assertTextSearched(fn (TextSearchQuery $q) => $q->textQuery === 'pizza');
    $fake->assertNothingAutocompleted();
    $fake->assertNothingGeocoded();
});
```

## Photos

Seed the bytes with withPhoto() and pair the fake with Storage::fake():

```php
use Illuminate\Support\Facades\Storage;
use RoundlyConsulting\GooglePlaces\DataTransferObjects\PhotoQuery;
use RoundlyConsulting\GooglePlaces\Facades\GooglePlaces;

it('stores the hero photo', function () {
    Storage::fake('public');
    $fake = GooglePlaces::fake()->withPhoto('JPEG-BYTES');

    GooglePlaces::photo('places/p1/photos/a', 1200, 800)->save('public', 'p1.jpg');

    Storage::disk('public')->assertExists('p1.jpg');
    $fake->assertPhotoRequested(fn (PhotoQuery $q) => $q->name === 'places/p1/photos/a' && $q->maxWidth === 1200);
});
```

## The health check

```php
use RoundlyConsulting\GooglePlaces\DataTransferObjects\ApiCheckResult;
use RoundlyConsulting\GooglePlaces\Facades\GooglePlaces;

config(['google-places.key' => 'test-key']);   // the command refuses to run without a key

$fake = GooglePlaces::fake()->withCheck([
    new ApiCheckResult('Places API (New)', true, 'Reachable and authorized.'),
    new ApiCheckResult('Routes API', false, 'Unreachable: timeout'),
    new ApiCheckResult('Geocoding API', true, 'Reachable and authorized.'),
]);

$this->artisan('google-places:check')->assertFailed();

$fake->assertChecked();
```

## Testing against the real client

To exercise request building, error classification, caching or rate limiting, keep the real client and fake the HTTP layer instead:

```php
use Illuminate\Support\Facades\Http;
use RoundlyConsulting\GooglePlaces\Exceptions\PlacesException;
use RoundlyConsulting\GooglePlaces\Facades\GooglePlaces;

config(['google-places.key' => 'test-key', 'google-places.rate_limits.places.enabled' => false]);

Http::fake([
    'places.googleapis.com/v1/places:searchText' => Http::response([
        'error' => [
            'status' => 'INVALID_ARGUMENT',
            'message' => 'API key not valid.',
            'details' => [['@type' => 'type.googleapis.com/google.rpc.ErrorInfo', 'reason' => 'API_KEY_INVALID']],
        ],
    ], 400),
]);

try {
    GooglePlaces::textSearch('coffee');
} catch (PlacesException $e) {
    expect($e->isDenied())->toBeTrue()
        ->and($e->isInvalidRequest())->toBeFalse()
        ->and($e->googleReason())->toBe('API_KEY_INVALID');
}
```

To run the package’s own suite from its repository:

```bash
composer test
```

## Show 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 crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/google-places-for-laravel.md)
