{"id":6590,"date":"2019-06-09T00:01:00","date_gmt":"2019-06-09T00:01:00","guid":{"rendered":"https:\/\/alexrusin.com\/?p=6590"},"modified":"2019-06-09T00:03:01","modified_gmt":"2019-06-09T00:03:01","slug":"testing-api-clients","status":"publish","type":"post","link":"https:\/\/blog.alexrusin.com\/testing-api-clients\/","title":{"rendered":"Testing API Clients"},"content":{"rendered":"\n

In this article I will describe an approach I use to test API clients. Since an API client is a boundary between your code and the outside world you should write as little code as possible to implement it. The client should strictly do its job by sending a request and returning a response. That is why we don’t really test the clients themselves but rather the code that processes the response from the client. Clients in the test code should be mocked in order not to hit a real API endpoint while running tests. <\/p>\n\n\n\n

The most frequent mistake in coding API clients is not covering all the scenarios. It is easy enough to write application code that processes a normal response from the remote server. But what happens if things go bad? SoapFault is thrown, important fields on the response are missing? That is why testing is important. It lets you go through all of the scenarios and make sure your application is ready to handle different cases.<\/p>\n\n\n\n

Setup<\/h3>\n\n\n\n

We will be testing a SOAP API client. Scenario is the following: we need to submit an invoice for tax purposes. I will be using Laravel framework because it lets you put things together a little bit faster, but the testing approach is generic and does not require any framework. Another reason I am using Laravel is to elaborate on the statement Facades Vs. Dependency Injection<\/em>. Let’s take a look at the setup.<\/p>\n\n\n\n

We are creating two tables: orders <\/em>and invoce_records<\/em>. In Laravel we also create two models Order <\/em>and InvoiceRecord<\/em> to access data from those tables. <\/p>\n\n\n\n

       Schema::create('orders', function (Blueprint $table) {\n            $table->increments('id');\n            $table->unsignedInteger('source_id');\n            $table->unsignedInteger('customer_id');\n            $table->string('status')->default('Pending');\n            $table->string('customer_email');\n            $table->double('total', 8, 2);\n            $table->double('tax_total', 8, 2);\n            $table->timestamps();\n        });\n\n        Schema::create('invoice_records', function (Blueprint $table) {\n            $table->increments('id');\n            $table->unsignedInteger('order_id');\n            $table->string('uuid', 50)->default('');\n            $table->boolean('error')->default(false);\n            $table->text('error_message')->nullable();\n            $table->timestamps();\n\n            $table->foreign('order_id')\n                ->references('id')->on('orders')\n                ->onDelete('cascade');\n        });\n\nclass Order extends Model\n{\n    protected $guarded = [];\n\n    public function invoiceRecord()\n    {\n        return $this->hasOne(InvoiceRecord::class);\n    }\n}\n\nclass InvoiceRecord extends Model\n{\n    protected $guarded = [];\n}<\/pre>\n\n\n\n

Below is our implementation of invoice connector that uses PHP’s native SoapClient<\/em> class.<\/p>\n\n\n\n

\/\/ app\/Connectors\/InvoiceConnector.php\n<?php\n\nnamespace App\\Connectors;\n\nclass InvoiceConnector\n{\n    private $client;\n\n    public function __construct() \n    {\n        $arrContextOptions = array(\n            \"ssl\"=>array( \n                \"verify_peer\"=>false, \n                \"verify_peer_name\"=>false,\n                'crypto_method' => STREAM_CRYPTO_METHOD_TLS_CLIENT)\n            );\n        \n        $options = array(\n                'soap_version'=>SOAP_1_2,\n                'exceptions'=>true,\n                'trace'=>1,\n                'cache_wsdl'=>WSDL_CACHE_NONE,\n                'stream_context' => stream_context_create($arrContextOptions)\n        );\n        \n        $this->client = new \\SoapClient('https:\/\/url\/dir\/file.wsdl', $options);\n    }\n    public function submitInvoice($order)\n    {\n        $result = $this->client->uploadInvoce([\n            'InvoiceID' => $order->source_id,\n            'TaxID' => $order->customer_id,\n            'Total' => $order->total, \n            'TaxTotal' => $order->tax_total\n        ]);\n\n        return $result;\n    }\n}<\/pre>\n\n\n\n

Finally our connector manager class under test looks like this. There is not much there but we will keep adding code to it as we write tests. <\/p>\n\n\n\n

\/\/ app\/Connectors\/ConnectorManager.php\n<?php\n\nnamespace App\\Connectors;\n\nclass ConnectorManager\n{\n    public function createInvoiceRecord($order)\n    {\n        \n    }\n}<\/pre>\n\n\n\n

Testing<\/h3>\n\n\n\n

The test checks if the code that receives responses from the API client creates a record in the invoice_records <\/em>table. If we receive a response back with UUID of the invoice got accepted by the remote system. <\/p>\n\n\n\n

We also created an OrderFactory<\/em> to facilitate the test.<\/p>\n\n\n\n

\/\/ tests\/Unit\/ConnectorManagerTest.php\n<?php\n\nnamespace Tests\\Unit;\n\nuse Tests\\TestCase;\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse App\\Order;\nuse App\\Connectors\\ConnectorManager;\n\nclass ConnectorManagerTest extends TestCase\n{\n    use RefreshDatabase;\n\n    \/** @test *\/\n    public function it_creates_invoice_record()\n    {\n        $order = factory(Order::class)->create();\n\n        $connectorManager = new ConnectorManager;\n        $connectorManager->createInvoiceRecord($order);\n       \n        $this->assertDatabaseHas('invoice_records', [\n            'order_id' => $order->id,\n            'uuid' => 'ABCD'\n        ]);\n    }\n}\n<\/pre>\n\n\n\n
\/\/ database\/factories\/OrderFactory.php\n<?php\n\nuse Faker\\Generator as Faker;\n\n$factory->define(App\\Order::class, function (Faker $faker) {\n    return [\n        'source_id' => $faker->randomNumber(8),\n        'customer_id' => $faker->randomNumber(6),\n        'customer_email' => $faker->email,\n        'total' => $faker->randomFloat(2, 100, 1000),\n        'tax_total' => $faker->randomFloat(2, 5, 20)\n    ];\n});\n<\/pre>\n\n\n\n

Obviously the test is going to fail because we do not have any code in the connector manager. Let’s try to go from red to green by writing code responsible for processing API response. <\/p>\n\n\n\n

\/\/ app\/Connectors\/ConnectorManager.php\n....\npublic function createInvoiceRecord($order)\n    {\n        $connector = new InvoiceConnector;\n\n        $result = $connector->submitInvoice($order);\n\n        if ($result->response->status == 200) {\n            $order->invoiceRecord()->create([\n                'uuid' => $result->response->uuid\n            ]);\n        }\n    }<\/pre>\n\n\n\n

The code above won’t work because as you may have noticed I put a fake WSDL in <\/em>the InvoiceConnector.<\/em><\/p>\n\n\n\n

1) Tests\\Unit\\ConnectorManagerTest::it_creates_invoice_record\nSoapFault: SOAP-ERROR: Parsing WSDL: Couldn't load from 'https:\/\/url\/dir\/file.wsdl' : failed to load external entity \"https:\/\/url\/dir\/file.wsdl\"<\/pre>\n\n\n\n

We can use dependency injection. Instead of instantiating InvoiceConnector <\/em> class in submitInvoice <\/em>method, we can pass already instantiated InvoiceConnector <\/em>object as an argument to the method. To keep things more structured, however, let’s pass the object in the constructor of ConnectorManager <\/em>instead of submitInvoice <\/em>method.<\/p>\n\n\n\n

\/\/ app\/Connectors\/ConnectorManager.php\n<?php\n\nnamespace App\\Connectors;\n\nclass ConnectorManager\n{\n    private $connector;\n\n    public function __construct(InvoiceConnector $connector)\n    {\n        $this->connector = $connector;\n    }\n\n    public function createInvoiceRecord($order)\n    {\n        $result = $this->connector->submitInvoice($order);\n\n        if ($result->response->status == 200) {\n            $order->invoiceRecord()->create([\n                'uuid' => $result->response->uuid\n            ]);\n        }\n    }\n}<\/pre>\n\n\n\n
\/\/ tests\/Unit\/ConnectorManagerTest.php\n<?php\n\nnamespace Tests\\Unit;\n\nuse Tests\\TestCase;\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse App\\Order;\nuse App\\Connectors\\ConnectorManager;\nuse App\\Connectors\\InvoiceConnector;\n\nclass ConnectorManagerTest extends TestCase\n{\n    use RefreshDatabase;\n\n    \/** @test *\/\n    public function it_creates_invoice_record()\n    {\n        $order = factory(Order::class)->create();\n\n        $connector = new InvoiceConnector;\n\n        $connectorManager = new ConnectorManager($connector);\n        $connectorManager->createInvoiceRecord($order);\n       \n        $this->assertDatabaseHas('invoice_records', [\n            'order_id' => $order->id,\n            'uuid' => 'ABCD'\n        ]);\n    }\n}\n<\/pre>\n\n\n\n

We are still going to have the same parsing WSDL error because we are still instantiating InvoiceConnector <\/em>class. Now we are doing it outside the ConnectorManager <\/em>class, in the client code. If only we could swap InvoiceConnector <\/em>implementation. That is what interfaces are for. Let’s create InvoiceConnectorInterface. <\/em><\/p>\n\n\n\n

\/\/ app\/Contracts\/InvoiceConnectorInterface.php\n<?php\n\nnamespace App\\Contracts;\n\ninterface InvoiceConnectorInterface\n{\n    public function submitInvoice($order);\n}<\/pre>\n\n\n\n
\/\/ app\/Connectors\/ConnectorManager.php\n<?php\n\nnamespace App\\Connectors;\n\nuse App\\Contracts\\InvoiceConnectorInterface;\n\nclass ConnectorManager\n{\n    private $connector;\n\n    public function __construct(InvoiceConnectorInterface $connector)\n    {\n        $this->connector = $connector;\n    }\n\n\/\/ rest of code...\n<\/pre>\n\n\n\n
\/\/ app\/Connectors\/InvoiceConnector.php\n<?php\n\nnamespace App\\Connectors;\n\nuse App\\Contracts\\InvoiceConnectorInterface;\n\nclass InvoiceConnector implements InvoiceConnectorInterface\n{\n\n\/\/ rest of code ...<\/pre>\n\n\n\n

We have to create a test double that we can use instead of InvoiceConnector <\/em>when testing. <\/p>\n\n\n\n

\/\/ tests\/Doubles\/InvoiceConnectorDouble.php\n<?php\n\nnamespace Tests\\Doubles;\n\nuse App\\Contracts\\InvoiceConnectorInterface;\n\nclass InvoiceConnectorDouble implements InvoiceConnectorInterface\n{\n    public function submitInvoice($order)\n    {\n        return (object) [\n            'response' => (object) [\n                'status' => 200, \n                'message' => 'OK',\n                'uuid' => 'ABCD'\n            ]\n        ];\n    }\n}<\/pre>\n\n\n\n
\/\/ tests\/Unit\/ConnectorManagerTest.php\n<?php\n\nnamespace Tests\\Unit;\n\nuse Tests\\TestCase;\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse App\\Order;\nuse App\\Connectors\\ConnectorManager;\nuse Tests\\Doubles\\InvoiceConnectorDouble;\n\nclass ConnectorManagerTest extends TestCase\n{\n    use RefreshDatabase;\n\n    \/** @test *\/\n    public function it_creates_invoice_record()\n    {\n        $order = factory(Order::class)->create();\n\n        $connector = new InvoiceConnectorDouble;\n\n        $connectorManager = new ConnectorManager($connector);\n        $connectorManager->createInvoiceRecord($order);\n       \n        $this->assertDatabaseHas('invoice_records', [\n            'order_id' => $order->id,\n            'uuid' => 'ABCD'\n        ]);\n    }\n}<\/pre>\n\n\n\n

Now we run phpunit and our test is passing. <\/p>\n\n\n\n

PHPUnit 7.4.3 by Sebastian Bergmann and contributors.\n\n.                                                                   1 \/ 1 (100%)\n\nTime: 366 ms, Memory: 18.00MB\n\nOK (1 test, 1 assertion)<\/pre>\n\n\n\n

Great. Now let’s write a test for a case when an error response is returned from the remote server. Does it mean we have to create another test double? What if we have five or six cases? Do we have to create a different test double for each that will fake the response we need? Not really. Let’s mock our test double using PHP Mockery framework<\/a> that comes with Laravel. Since Laravel uses Mockery internally for facades, Laravel already has integrated Mockery with phpunit. If you would like to know how to integrate Mockery with phpunit you can read about it here<\/a>.<\/p>\n\n\n\n

\/\/ tests\/Unit\/ConnectoManagerTest.php\n<?php\n\nnamespace Tests\\Unit;\n\nuse Mockery;\nuse Tests\\TestCase;\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse App\\Order;\nuse App\\Connectors\\ConnectorManager;\nuse Tests\\Doubles\\InvoiceConnectorDouble;\n\nclass ConnectorManagerTest extends TestCase\n{\n    use RefreshDatabase;\n\n    \/** @test *\/\n    public function it_creates_invoice_record_with_error()\n    {\n        $order = factory(Order::class)->create();\n\n        $connector = Mockery::mock(InvoiceConnectorDouble::class);\n        $connector->shouldReceive('submitInvoice')\n            ->with($order)\n            ->andReturn((object) [\n                'response' => (object) [\n                    'status' => 623,\n                    'message' => 'Invoice invalid format'\n                ]\n            ]);\n\n        \n        $connectorManager = new ConnectorManager($connector);\n        $connectorManager->createInvoiceRecord($order);\n       \n        $this->assertDatabaseHas('invoice_records', [\n            'order_id' => $order->id,\n            'uuid' => '',\n            'error' => true,\n            'error_message' => 'Invoice invalid format'\n        ]);\n    }\n}\n<\/pre>\n\n\n\n

The code above creates a mock from InvoceConnectorDouble <\/em>and sets expectations for submitInvoice <\/em>method. Then we are asserting that we are expecting to see an error recorded int the database. When running the test we will get the following:<\/p>\n\n\n\n

There was 1 failure:\n\n1) Tests\\Unit\\ConnectorManagerTest::it_creates_invoice_record_with_error\nFailed asserting that a row in the table [invoice_records] matches the attributes {\n    \"order_id\": 1,\n    \"uuid\": \"\",\n    \"error\": true,\n    \"error_message\": \"Invoice invalid format\"\n}.\n\nThe table is empty.\n\nC:\\LaravelProjects\\laravel57\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Testing\\Concerns\\InteractsWithDatabase.php:23\nC:\\LaravelProjects\\laravel57\\tests\\Unit\\ConnectorManagerTest.php:55\n\nFAILURES!\nTests: 1, Assertions: 2, Failures: 1.<\/pre>\n\n\n\n

This is expected because we do not have code that will process error response. Let’s write it. <\/p>\n\n\n\n

\/\/ app\/Connectors\/ConnectorManager.php\n\n\/\/ previous code ....\n\n    public function createInvoiceRecord($order)\n    {\n        $result = $this->connector->submitInvoice($order);\n\n        if ($result->response->status == 200) {\n            $order->invoiceRecord()->create([\n                'uuid' => $result->response->uuid\n            ]);\n        } else {\n            $order->invoiceRecord()->create([\n               'error' => true,\n               'error_message' => 'Invoice invalid format' \n            ]);\n        }\n    }<\/pre>\n\n\n\n

Now our test is passing. <\/p>\n\n\n\n

PHPUnit 7.4.3 by Sebastian Bergmann and contributors.\n\n.                                                                   1 \/ 1 (100%)\n\nTime: 4.67 seconds, Memory: 18.00MB\n\nOK (1 test, 2 assertions)<\/pre>\n\n\n\n

There are two more test cases I can think of that we need to cover. First is when a SoapFault <\/em>is thrown. The second one is when result returned from InvoiceConnectorManager <\/em>object wont have “response” property, so $result->response will blow up. I won’t go into details about testing these cases and just show you my test and connector manger classes below because the idea is the same.<\/p>\n\n\n\n

\/\/ tests\/Unit\/ConnectorManagerTest.php\n<?php\n\nnamespace Tests\\Unit;\n\nuse Mockery;\nuse Tests\\TestCase;\nuse Illuminate\\Foundation\\Testing\\RefreshDatabase;\nuse App\\Order;\nuse App\\Connectors\\ConnectorManager;\nuse Tests\\Doubles\\InvoiceConnectorDouble;\n\nclass ConnectorManagerTest extends TestCase\n{\n    use RefreshDatabase;\n\n    public function setUp()\n    {\n        parent::setUp();\n        $this->order = factory(Order::class)->create();\n        $this->connector = Mockery::mock(InvoiceConnectorDouble::class);\n    }\n\n    private function createInvoiceRecord()\n    {\n        $connectorManager = new ConnectorManager($this->connector);\n        $connectorManager->createInvoiceRecord($this->order);\n    }\n\n    \/** @test *\/\n    public function it_creates_invoice_record()\n    {\n        $this->connector->shouldReceive('submitInvoice')\n            ->with($this->order)\n            ->andReturn((object) [\n                'response' => (object) [\n                    'status' => 200,\n                    'message' => 'OK',\n                    'uuid' => 'ABCD'\n                ]\n            ]);\n\n        $this->createInvoiceRecord();\n       \n        $this->assertDatabaseHas('invoice_records', [\n            'order_id' => $this->order->id,\n            'uuid' => 'ABCD'\n        ]);\n    }\n\n    \/** @test *\/\n    public function it_creates_invoice_record_with_error()\n    {\n        $this->connector->shouldReceive('submitInvoice')\n            ->with($this->order)\n            ->andReturn((object) [\n                'response' => (object) [\n                    'status' => 623,\n                    'message' => 'Invoice invalid format'\n                ]\n            ]);\n\n        $this->createInvoiceRecord();\n       \n        $this->assertDatabaseHas('invoice_records', [\n            'order_id' => $this->order->id,\n            'uuid' => '',\n            'error' => true,\n            'error_message' => 'Invoice invalid format'\n        ]);\n    }\n\n    \/** @test *\/\n    public function it_throws_soap_fault()\n    {\n        $this->connector->shouldReceive('submitInvoice')\n            ->with($this->order)\n            ->andThrow(new \\SoapFault('test', 'There was an error connecting'));\n\n        $this->createInvoiceRecord();\n\n        $this->assertDatabaseHas('invoice_records', [\n            'order_id' => $this->order->id,\n            'uuid' => '',\n            'error' => true,\n            'error_message' => 'There was an error connecting'\n        ]);\n    }\n\n    \/** @test *\/\n    public function it_records_error_if_response_property_does_not_exist()\n    {\n        $this->connector->shouldReceive('submitInvoice')\n            ->with($this->order)\n            ->andReturn((object) []);\n\n        $this->createInvoiceRecord();\n       \n        $this->assertDatabaseHas('invoice_records', [\n            'order_id' => $this->order->id,\n            'uuid' => '',\n            'error' => true,\n            'error_message' => 'Response is malformed'\n        ]);\n    }\n\n}<\/pre>\n\n\n\n
\/\/ app\/Connectors\/ConnectorManager.php\n<?php\n\nnamespace App\\Connectors;\n\nuse App\\Contracts\\InvoiceConnectorInterface;\n\nclass ConnectorManager\n{\n    private $connector;\n\n    public function __construct(InvoiceConnectorInterface $connector)\n    {\n        $this->connector = $connector;\n    }\n\n    public function createInvoiceRecord($order)\n    {\n        try {\n            $result = $this->connector->submitInvoice($order);\n        } catch (\\Throwable $e) {\n            $order->invoiceRecord()->create([\n                'error' => true,\n                'error_message' => $e->getMessage()\n            ]);\n            return;\n        }\n\n        if (!$result || empty($result->response)) { \n            $order->invoiceRecord()->create([\n                'error' => true,\n                'error_message' => 'Response is malformed'\n            ]);\n\n            return;\n        }\n\n        $result = $result->response;\n        \n        if ($result->status == 200) {\n            $order->invoiceRecord()->create([\n                'uuid' => $result->uuid\n            ]);\n        } else {\n            $order->invoiceRecord()->create([\n                'error' => true,\n                'error_message' => $result->message\n            ]);\n        }\n\n        \n    }\n}<\/pre>\n\n\n\n

As you may have noticed we wrote tests first before we wrote our code. In a way, tests drove our design. We used dependency injection and isolated InvoiceConnector<\/em> with an interface. We did that not because dependency injection and inversion of control are the right things to do. We did that because we wanted to make our code testable. <\/p>\n\n\n\n

Facades Vs. Dependency Injection <\/h3>\n\n\n\n

Now let’s take a look at “Facades Vs. Dependency Injection”. Since Laravel has concept of facades, we could have used real time facade instead of dependency injection. Our code would look something like this:<\/p>\n\n\n\n

\/\/ in app\/Connectors\/ConnectorManager.php\n\nuse Facades\\App\\Connectors\\InvoiceConnector;\n\/\/ some code ...\npublic function createInvoiceRecord($order)\n{\n        try {\n            InvoiceConnector::submitInvoice($order);\n        } catch (\\Throwable $e) {\n            $order->invoiceRecord()->create([\n                'error' => true,\n                'error_message' => $e->getMessage()\n            ]);\n            return;\n        }\n\/\/ rest of the code ....\n\n}<\/pre>\n\n\n\n

Then in hour test class we would have something like this:<\/p>\n\n\n\n

\/\/ tests\/Unit\/ConnectorManagerTest.php\n\n\/\/ some code ...\nuse Facades\\App\\Connectors\\InvoiceConnector;\n\/\/ some code ...\n\n        InvoiceConnector::shouldReceive('submitInvoice')\n            ->with($this->order)\n            ->andReturn((object) [\n                'response' => (object) [\n                    'status' => 200,\n                    'message' => 'OK',\n                    'uuid' => 'ABCD'\n                ]\n            ]);\n\/\/ rest of the code ...<\/pre>\n\n\n\n

I consider Laravel’s real-time facades to be useful for testing because you don’t have to think about how to better use dependency injection (should it be a constructor dependency injection, or method dependency injection). Laravel’s IoC container will resolve the class behind the scenes and hand to the method. When testing, it uses Mockery under the hood and gives you a mock instance of your class. Unfortunately, when IoC resolves a mock it first instantiates the class, and then gets the name of the class to create a mock. If class is instantiated before it gets mocked the constructor method gets triggered. In our case this will cause an issue, because we are instantiating SoapClient in the constructor and using fake WSDL. If we were to use a real WSDL we would have made an external call to check get the description. This is not desirable when running tests. If you run the tests on a computer without internet connection, the tests will blow up. That is why I prefer to use dependency injection to Laravel’s real-time facades.<\/p>\n","protected":false},"excerpt":{"rendered":"

Since an API client is a boundary between your code and the outside world you should write as little code as possible to implement it. The client should strictly do its job by sending a request and returning a response. That is why we don’t really test the clients themselves but rather the code that processes the response from the client.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"footnotes":""},"categories":[12,10],"tags":[],"class_list":["post-6590","post","type-post","status-publish","format-standard","hentry","category-laravel","category-php-mysql-development"],"yoast_head":"\nTesting API Clients | Alex Rusin Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.alexrusin.com\/testing-api-clients\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Testing API Clients | Alex Rusin Blog\" \/>\n<meta property=\"og:description\" content=\"Since an API client is a boundary between your code and the outside world you should write as little code as possible to implement it. The client should strictly do its job by sending a request and returning a response. That is why we don't really test the clients themselves but rather the code that processes the response from the client.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blog.alexrusin.com\/testing-api-clients\/\" \/>\n<meta property=\"og:site_name\" content=\"Alex Rusin Blog\" \/>\n<meta property=\"article:published_time\" content=\"2019-06-09T00:01:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2019-06-09T00:03:01+00:00\" \/>\n<meta name=\"author\" content=\"alexrusin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"alexrusin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/blog.alexrusin.com\/testing-api-clients\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/blog.alexrusin.com\/testing-api-clients\/\"},\"author\":{\"name\":\"alexrusin\",\"@id\":\"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a\"},\"headline\":\"Testing API Clients\",\"datePublished\":\"2019-06-09T00:01:00+00:00\",\"dateModified\":\"2019-06-09T00:03:01+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/blog.alexrusin.com\/testing-api-clients\/\"},\"wordCount\":1063,\"publisher\":{\"@id\":\"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a\"},\"articleSection\":[\"Laravel\",\"PHP MySQL Development\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.alexrusin.com\/testing-api-clients\/\",\"url\":\"https:\/\/blog.alexrusin.com\/testing-api-clients\/\",\"name\":\"Testing API Clients | Alex Rusin Blog\",\"isPartOf\":{\"@id\":\"https:\/\/blog.alexrusin.com\/#website\"},\"datePublished\":\"2019-06-09T00:01:00+00:00\",\"dateModified\":\"2019-06-09T00:03:01+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/blog.alexrusin.com\/testing-api-clients\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.alexrusin.com\/testing-api-clients\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.alexrusin.com\/testing-api-clients\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/blog.alexrusin.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Testing API Clients\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.alexrusin.com\/#website\",\"url\":\"https:\/\/blog.alexrusin.com\/\",\"name\":\"Alex Rusin\",\"description\":\"Web Development Blog\",\"publisher\":{\"@id\":\"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.alexrusin.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a\",\"name\":\"alexrusin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.alexrusin.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/c36ef231f9e0b11371891eb84360f4bc?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/c36ef231f9e0b11371891eb84360f4bc?s=96&d=mm&r=g\",\"caption\":\"alexrusin\"},\"logo\":{\"@id\":\"https:\/\/blog.alexrusin.com\/#\/schema\/person\/image\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Testing API Clients | Alex Rusin Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.alexrusin.com\/testing-api-clients\/","og_locale":"en_US","og_type":"article","og_title":"Testing API Clients | Alex Rusin Blog","og_description":"Since an API client is a boundary between your code and the outside world you should write as little code as possible to implement it. The client should strictly do its job by sending a request and returning a response. That is why we don't really test the clients themselves but rather the code that processes the response from the client.","og_url":"https:\/\/blog.alexrusin.com\/testing-api-clients\/","og_site_name":"Alex Rusin Blog","article_published_time":"2019-06-09T00:01:00+00:00","article_modified_time":"2019-06-09T00:03:01+00:00","author":"alexrusin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"alexrusin","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/blog.alexrusin.com\/testing-api-clients\/#article","isPartOf":{"@id":"https:\/\/blog.alexrusin.com\/testing-api-clients\/"},"author":{"name":"alexrusin","@id":"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a"},"headline":"Testing API Clients","datePublished":"2019-06-09T00:01:00+00:00","dateModified":"2019-06-09T00:03:01+00:00","mainEntityOfPage":{"@id":"https:\/\/blog.alexrusin.com\/testing-api-clients\/"},"wordCount":1063,"publisher":{"@id":"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a"},"articleSection":["Laravel","PHP MySQL Development"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/blog.alexrusin.com\/testing-api-clients\/","url":"https:\/\/blog.alexrusin.com\/testing-api-clients\/","name":"Testing API Clients | Alex Rusin Blog","isPartOf":{"@id":"https:\/\/blog.alexrusin.com\/#website"},"datePublished":"2019-06-09T00:01:00+00:00","dateModified":"2019-06-09T00:03:01+00:00","breadcrumb":{"@id":"https:\/\/blog.alexrusin.com\/testing-api-clients\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.alexrusin.com\/testing-api-clients\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/blog.alexrusin.com\/testing-api-clients\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blog.alexrusin.com\/"},{"@type":"ListItem","position":2,"name":"Testing API Clients"}]},{"@type":"WebSite","@id":"https:\/\/blog.alexrusin.com\/#website","url":"https:\/\/blog.alexrusin.com\/","name":"Alex Rusin","description":"Web Development Blog","publisher":{"@id":"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.alexrusin.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a","name":"alexrusin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.alexrusin.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/c36ef231f9e0b11371891eb84360f4bc?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/c36ef231f9e0b11371891eb84360f4bc?s=96&d=mm&r=g","caption":"alexrusin"},"logo":{"@id":"https:\/\/blog.alexrusin.com\/#\/schema\/person\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/posts\/6590","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/comments?post=6590"}],"version-history":[{"count":24,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/posts\/6590\/revisions"}],"predecessor-version":[{"id":6618,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/posts\/6590\/revisions\/6618"}],"wp:attachment":[{"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/media?parent=6590"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/categories?post=6590"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/tags?post=6590"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}