Laravel Testing: Mocking/Faking External 3rd Party APIs

Laravel PHPUnit Mocking

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): array
2{
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": false
46 },
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...

The full tutorial [11 mins, 2004 words] is only for Premium Members

Become a Premium Member for $129/year or $29/month

Recent Premium Tutorials