One of the most common questions about automated testing in Laravel is how to write tests for the usage of some external API. There are three ways to do that, and I will show all of those in this article.
This is a text-based excerpt of one chapter in my course Advanced Laravel Testing
We will talk about:
- Faking HTTP requests
- Mocking Classes
- Using External Sandboxes
As an example, let's imagine you use an external service to get the information by IP address, with Laravel HTTP Client.
1private static function getIpData(string $ip): array2{3 $token = config('location.ipdata.token', '');4 5 $url = "https://api.ipdata.co/{$ip}?api-key=".$token;6 7 return Http::get($url)->throw()->json();8}
How would you test if it works?
The code above is from a real open-source project Monica. Let's take a look at our options.
Option 1. Faking HTTP Requests with Http::fake()
The project authors fake the HTTP requests for external services.
They also use a thing called Fixtures, which are just pre-prepared sets of data. So, if we take a look at tests/Fixtures
, there are prepared sets of data in JSON format. Ipdata.json contains an example response from an external service about IP. So, the IP address should contain this example information:
1{ 2 "ip": "12.34.56.78", 3 "is_eu": true, 4 "city": "Paris", 5 "region": "\u00cele-de-France", 6 "region_code": "IDF", 7 "country_name": "France", 8 "country_code": "FR", 9 "continent_name": "Europe",10 "continent_code": "EU",11 "latitude": 0,12 "longitude": 0,13 "postal": "75000",14 "calling_code": "33",15 "flag": "https://ipdata.co/flags/fr.png",16 "emoji_flag": "\ud83c\uddeb\ud83c\uddf7",17 "emoji_unicode": "U+1F1EB U+1F1F7",18 "languages": [19 {20 "name": "French",21 "native": "Fran\u00e7ais"22 }23 ],24 "currency": {25 "name": "Euro",26 "code": "EUR",27 "symbol": "\u20ac",28 "native": "\u20ac",29 "plural": "euros"30 },31 "time_zone": {32 "name": "Europe/Paris",33 "abbr": "CET",34 "offset": "+0100",35 "is_dst": false,36 "current_time": "2021-11-01T00:00:00+01:00"37 },38 "threat": {39 "is_tor": false,40 "is_proxy": false,41 "is_anonymous": false,42 "is_known_attacker": false,43 "is_known_abuser": false,44 "is_threat": false,45 "is_bogon": false46 },47 "count": "0"48}
Now, how those example JSON responses are used in tests? This ipdata.json
file usage can be found in the tests/Unit/Helpers/RequestHelperTest.php
method get_infos_from_ip()
:
1class RequestHelperTest extends TestCase 2{ 3 /** @test */ 4 public function get_infos_from_ip() 5 { 6 config(['location.ipdata.token' => 'test']); 7 8 $body = file_get_contents(base_path('tests/Fixtures/Helpers/ipdata.json')); 9 Http::fake([10 'https://api.ipdata.co/*' => Http::response($body, 200),11 ]);12 13 $this->assertEquals(14 [15 'country' => 'FR',16 'currency' => 'EUR',17 'timezone' => 'Europe/Paris',18 ],19 RequestHelper::infos('test')20 );21 }22}
Now, what does this test do?
First, it overwrites the config, so we don't expose and don't use the live token of that service ipdata.co.
Then, we use Http::fake()
. So you can fake the request to any URL, here https://api.ipdata.co/*
they use asterisks so that whatever URL with api.ipdata.co
would be faked with the response. That response comes from the ipdata.json
you saw earlier.
So we fake the API request to api.ipdata.co
so it would not make the real HTTP request, but instead...