Google Search Console - v1.0.0
Testing
Patterns for testing your own code against artisanpack-ui/google-search-console. The package's own suite (Pest, 61 tests, 154 assertions) is the reference implementation — look at tests/Feature/* if you want copy-paste-ready examples.
Test bootstrap
The package's own base test case wires up:
- Orchestra Testbench for the Laravel harness.
- The base
artisanpack-ui/googleservice provider (needed forTokenManager,ScopeRegistry, etc.). - Livewire's service provider (needed for
Livewire::test()). - Local test stubs for
artisanpack-ui/cms-frameworkautoloaded into the real cms-framework namespace viaautoload-dev(so the bridge can be tested without pulling in the framework's transitive deps). - Static-state resets:
BaseInstalled::reset(),CmsFrameworkInstalled::reset()in setUp/tearDown.
An app-level test that talks to this package doesn't need any of this — install the package normally and Testbench (or your normal Laravel TestCase) will bootstrap it.
use ArtisanPackUI\GoogleSearchConsole\GoogleSearchConsoleServiceProvider;
use Orchestra\Testbench\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
protected function getPackageProviders( $app ): array
{
return [
ArtisanPackUI\Google\GoogleServiceProvider::class,
Livewire\LivewireServiceProvider::class,
GoogleSearchConsoleServiceProvider::class,
];
}
}
Fake Google
Every call goes through the standard Laravel HTTP client, so Http::fake() is enough. Both endpoint shapes:
Fake a single query
use Illuminate\Support\Facades\Http;
Http::fake( [
'*' => Http::response( [
'rows' => [
[ 'keys' => [ 'buy shoes' ], 'clicks' => 42, 'impressions' => 500, 'ctr' => 0.084, 'position' => 3.2 ],
],
], 200 ),
] );
Fake the two-call performance query
The PerformanceOverviewFetcher fires two queries — totals first, then trend — in that order:
Http::fakeSequence()
->push( [
'rows' => [
[ 'keys' => [], 'clicks' => 100, 'impressions' => 1000, 'ctr' => 0.1, 'position' => 8.5 ],
],
], 200 )
->push( [
'rows' => [
[ 'keys' => [ '2026-01-01' ], 'clicks' => 40, 'impressions' => 400, 'ctr' => 0.1, 'position' => 8.6 ],
[ 'keys' => [ '2026-01-02' ], 'clicks' => 60, 'impressions' => 600, 'ctr' => 0.1, 'position' => 8.4 ],
],
], 200 );
Fake an API error
Http::fake( [
'*' => Http::response( [ 'error' => 'permission denied' ], 403 ),
] );
// Client will throw ReportingException::apiError( 403, '...' )
Fake a transport error
Http::fake( function (): void {
throw new Illuminate\Http\Client\ConnectionException( 'connection refused' );
} );
// Client will throw ReportingException::transportFailure( $e )
Testing the client
use ArtisanPackUI\GoogleSearchConsole\Reporting\DateRange;
use ArtisanPackUI\GoogleSearchConsole\Reporting\SearchAnalyticsClient;
use ArtisanPackUI\GoogleSearchConsole\Reporting\SearchAnalyticsRequest;
use ArtisanPackUI\GoogleSearchConsole\Support\BaseInstalled;
use Illuminate\Http\Client\Factory as HttpFactory;
BaseInstalled::setForTesting( true );
config()->set( 'google-search-console.reporting.site_url', 'https://example.com/' );
$client = new SearchAnalyticsClient(
config: app( 'config' ),
http: app( HttpFactory::class ),
tokens: makeGscStubTokenManager( 'test-access-token' ),
);
$response = $client->query(
new SearchAnalyticsRequest(
dateRange: new DateRange( '2026-01-01', '2026-01-07' ),
dimensions: [ 'query' ],
),
makeGscConnectedConnection(),
);
expect( $response->rows() )->toBe( [
[ 'query' => 'buy shoes', 'clicks' => 42.0, 'impressions' => 500.0, 'ctr' => 0.084, 'position' => 3.2 ],
] );
Where makeGscStubTokenManager() and makeGscConnectedConnection() are helpers the package's own tests/Pest.php defines — copy them into your own Pest.php for the same patterns. See the actual source in the package for the working versions.
Testing the fetchers
use ArtisanPackUI\GoogleSearchConsole\Reporting\PerformanceOverviewFetcher;
Http::fakeSequence()
->push( [ 'rows' => [ [ 'keys' => [], 'clicks' => 100, 'impressions' => 1000, 'ctr' => 0.1, 'position' => 8.5 ] ] ], 200 )
->push( [ 'rows' => [] ], 200 );
$fetcher = new PerformanceOverviewFetcher( app( SearchAnalyticsClient::class ) );
$data = $fetcher->fetch( makeGscConnectedConnection(), DateRange::lastDays( 7 ) );
expect( $data->totals[ 'clicks' ] )->toBe( 100.0 );
expect( $data->hasData )->toBeTrue();
Testing the HTTP endpoints
Stub the resolver and client via ->instance() so the controller runs on synthetic data:
use ArtisanPackUI\GoogleSearchConsole\Support\GoogleConnectionResolver;
use ArtisanPackUI\GoogleSearchConsole\Reporting\SearchAnalyticsClient;
app()->instance( GoogleConnectionResolver::class, new class extends GoogleConnectionResolver {
public function forUser( ?Illuminate\Contracts\Auth\Authenticatable $user ): ?ArtisanPackUI\Google\Models\GoogleConnection
{
return makeGscConnectedConnection();
}
} );
Http::fake( [ '*' => Http::response( [ 'rows' => [] ], 200 ) ] );
$this->actingAs( $user )
->getJson( '/google-search-console/performance?days=7' )
->assertOk();
Testing the Livewire components
The package's tests/Feature/LivewireComponentsTest.php covers the reference patterns:
use ArtisanPackUI\GoogleSearchConsole\Livewire\PerformanceCard;
use Livewire\Livewire;
// Stub the resolver + client bindings (see above).
Http::fakeSequence()
->push( [ 'rows' => [ [ 'keys' => [], 'clicks' => 137, 'impressions' => 900, 'ctr' => 0.152, 'position' => 4.1 ] ] ], 200 )
->push( [ 'rows' => [ [ 'keys' => [ '2026-01-01' ], 'clicks' => 137, 'impressions' => 900, 'ctr' => 0.152, 'position' => 4.1 ] ] ], 200 );
Livewire::test( PerformanceCard::class )
->assertSet( 'baseInstalled', true )
->assertSet( 'hasData', true )
->assertSet( 'totals.clicks', 137.0 )
->assertSee( 'Search performance' );
Livewire::test() mounts the component, runs mount() (which calls refresh()), and gives you a fluent-assertion object.
Testing the CMS framework bridge
The package's tests/Feature/CmsFrameworkBridgeTest.php uses stub classes in tests/Stubs/CmsFrameworkAdminWidgets/ autoloaded into the real cms-framework namespace via autoload-dev:
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/",
"ArtisanPackUI\\CMSFramework\\Modules\\AdminWidgets\\": "tests/Stubs/CmsFrameworkAdminWidgets/"
}
}
Copy the same pattern into your own package if you want to test bridge integrations without pulling the full cms-framework dep tree in.
Assertion pattern:
use ArtisanPackUI\CMSFramework\Modules\AdminWidgets\Services\AdminWidgetManager;
use ArtisanPackUI\GoogleSearchConsole\Bridges\CmsFramework\AdminWidgets\PerformanceCardWidget;
// TestCase binds AdminWidgetManager as a singleton in defineEnvironment,
// so the boot-time registrations are visible here.
$registered = app( AdminWidgetManager::class )->getRegistered();
expect( $registered )->toBe( [
'google-search-console.performance-card' => PerformanceCardWidget::class,
'google-search-console.top-queries-table' => TopQueriesTableWidget::class,
'google-search-console.top-pages-table' => TopPagesTableWidget::class,
] );
Testing scope registration
use ArtisanPackUI\Google\Scopes\ScopeRegistry;
use ArtisanPackUI\GoogleSearchConsole\Support\BaseInstalled;
BaseInstalled::setForTesting( true );
expect( app( ScopeRegistry::class )->all() )
->toContain( 'https://www.googleapis.com/auth/webmasters.readonly' );
Testing the cache
The SearchAnalyticsClient caches successful responses under google-search-console:query:* for the configured TTL:
config()->set( 'google-search-console.reporting.cache_ttl', 300 );
Http::fake( [ '*' => Http::response( [ 'rows' => [ /* ... */ ] ], 200 ) ] );
$client->query( $request, $connection ); // hits Google
$client->query( $request, $connection ); // hits the cache
Http::assertSentCount( 1 );
To exercise cache misses, either set cache_ttl to 0 or vary the request payload between calls.
Running the package's own tests
From the package directory:
composer test # runs Pest
composer lint # php-cs-fixer --dry-run + phpcs
composer fix # php-cs-fixer fix
From this dev app (with the package symlinked):
cd packages/google-search-console
vendor/bin/pest --compact
See also
- The package's own
tests/Feature/*— reference implementations for every pattern above. - Reporting/Caching — cache behavior in depth.
- Troubleshooting — common test failures decoded.