Set up local development for SerpApi with response caching, fixture recording, and offline testing. SerpApi charges per search, so caching results locally is critical for cost-effective development.
import serpapi, json, os, hashlib
def record_fixture(params: dict, fixtures_dir="tests/fixtures"):
"""Run a real search and save the response as a fixture file."""
os.makedirs(fixtures_dir, exist_ok=True)
client = serpapi.Client(api_key=os.environ["SERPAPI_API_KEY"])
result = client.search(**params)
# Deterministic filename from params
key = hashlib.md5(json.dumps(params, sort_keys=True).encode()).hexdigest()[:12]
path = os.path.join(fixtures_dir, f"{params['engine']}_{key}.json")
with open(path, "w") as f:
json.dump(dict(result), f, indent=2)
print(f"Recorded: {path}")
# Record fixtures for common queries
record_fixture({"engine": "google", "q": "python tutorial", "num": 5})
record_fixture({"engine": "youtube", "search_query": "react hooks"})
record_fixture({"engine": "bing", "q": "machine learning"})
import json, os
class MockSerpApiClient:
def __init__(self, fixtures_dir="tests/fixtures"):
self.fixtures_dir = fixtures_dir
def search(self, **params):
key = hashlib.md5(json.dumps(params, sort_keys=True).encode()).hexdigest()[:12]
path = os.path.join(self.fixtures_dir, f"{params['engine']}_{key}.json")
if os.path.exists(path):
with open(path) as f:
return json.load(f)
raise FileNotFoundError(f"No fixture for {params}. Run record_fixture() first.")
// tests/serpapi.test.ts
import { describe, it, expect, vi } from 'vitest';
import { readFileSync } from 'fs';
vi.mock('serpapi', () => ({
getJson: vi.fn(async (params) => {
const fixture = JSON.parse(
readFileSync(`tests/fixtures/google_sample.json`, 'utf-8')
);
return fixture;
}),
}));
describe('Search Service', () => {
it('parses organic results', async () => {
const { getJson } = await import('serpapi');
const result = await getJson({ engine: 'google', q: 'test' });
expect(result.organic_results).toBeDefined();
expect(result.organic_results[0]).toHaveProperty('title');
expect(result.organic_results[0]).toHaveProperty('link');
});
});
# .env.development (uses real API, low num for cost)
SERPAPI_API_KEY=real-key-here
SERPAPI_DEFAULT_NUM=3
# .env.test (uses fixtures, no API calls)
SERPAPI_API_KEY=not-needed
SERPAPI_USE_FIXTURES=true
| Error | Cause | Solution |
|---|---|---|
FileNotFoundError fixture |
Missing fixture | Run record_fixture() with real API key |
| Stale fixtures | Search results changed | Re-record periodically |
Invalid API key in dev |
Env not loaded | Check .env.development loading |
Proceed to serpapi-sdk-patterns for production patterns.