This is where we get to kind of more "advanced" faking of stuff, which is faking external services/APIs.
This is probably the most tricky part of testing, and I get a lot of questions about it. So, how do you fake an external service?
There are three ways, which we will discuss in the upcoming three lessons:
For the first one, let's examine the example from an open-source monicahq/monica project.
As you can see in the example below, the Http::fake()
is used, similar to the examples from previous lessons about other Laravel Facades.
class GetGPSCoordinateTest extends TestCase{ // ... /** @test */ public function it_gets_gps_coordinates() { $body = file_get_contents(base_path('tests/Fixtures/Services/Address/GetGPSCoordinateSampleResponse.json')); Http::fake([ 'fake.com/v1/*' => Http::response($body, 200), ]); $address = Address::factory()->create(); $request = [ 'address_id' => $address->id, ]; (new GetGPSCoordinate($request))->handle(); $address->refresh(); $this->assertDatabaseHas('addresses', [ 'id' => $address->id, 'latitude' => '34.0736204', 'longitude' => '-118.4003563', ]); $this->assertInstanceOf( Address::class, $address ); } // ...}
Now, what does this test do?
First, it gets the content of the fixture file, and I need to explain what that is. Fixtures are just pre-prepared example sets of data in JSON format. GetGPSCoordinateSampleResponse.json contains an example response from an external service about the IP address:
[ { "place_id": "200788090", "licence": "\u00a9 LocationIQ.com CC BY 4.0, Data \u00a9 OpenStreetMap contributors, ODbL 1.0", "osm_type": "relation", "osm_id": "3503615", "boundingbox": ["34.05269", "34.112456", "-118.4270732", "-118.3715358"], "lat": "34.0736204", "lon": "-118.4003563", "display_name": "Beverly Hills, Los Angeles County, California, USA", "class": "place", "type": "city", "importance": 0.72188891692726, "icon": "https://locationiq.org/static/images/mapicons/poi_place_city.p.20.png" }]
So yeah, they just read this example response from the JSON file.
Then, they use Http::fake()
to fake the request to any URL. Here, fake.com/v1/*
uses asterisks so that the response is faked with whatever URL within fake.com/v1
. That response comes from the GetGPSCoordinateSampleResponse.json
you saw above.
So we faked the API request to fake.com
so that it would not make the actual HTTP request but instead return data from the GetGPSCoordinateSampleResponse.json
file.
Finally, they assert that the address is in the database.
But what is inside the GetGPSCoordinate
class?
app/Domains/Vault/ManageAddresses/Services/GetGPSCoordinate.php:
class GetGPSCoordinate extends QueuableService implements ServiceInterface{ private Address $address; public function rules(): array { return [ 'address_id' => 'required|integer|exists:addresses,id', ]; } public function execute(array $data): void { $this->validate(); $this->getCoordinates(); } private function validate(): void { $this->validateRules($this->data); $this->address = Address::findOrFail($this->data['address_id']); } private function getCoordinates() { $query = $this->buildQuery(); try { $response = Http::get($query)->throw(); $this->address->latitude = $response->json('0.lat'); $this->address->longitude = $response->json('0.lon'); $this->address->save(); } catch (RequestException $e) { Log::error('Error calling location_iq: '.$e->response->json()['error']); $this->fail($e); } } private function buildQuery(): string { if (is_null(config('monica.location_iq_api_key'))) { throw new EnvVariablesNotSetException('Env variables are not set for Location IQ'); } $query = http_build_query([ 'format' => 'json', 'key' => config('monica.location_iq_api_key'), 'q' => MapHelper::getAddressAsString($this->address), ]); return Str::finish(config('monica.location_iq_url'), '/').'search.php?'.$query; }}
The class builds the HTTP query and makes the HTTP request to an external service. Monica uses LocationIQ service for translating addresses to latitude/longitude coordinates.
In other words, the sequence is this:
Http::fake()