This simple class will allow us to get the current weather of any city listed in the OpenWeatherApi:

$forecaster=newForecaster;$weather=$forecaster->weatherIn('London');// echo'ing out the JSON of the weather in Londonecho(json_encode($weather));=>{"coord":{"lon":-0.13,"lat":51.51},"weather":[{"id":300,"main":"Drizzle","description":"light intensity drizzle","icon":"09d"}],"base":"stations","main":{"temp":280.32,"pressure":1012,"humidity":81,"temp_min":279.15,"temp_max":281.15},"visibility":10000,"wind":{"speed":4.1,"deg":80},"clouds":{"all":90},"dt":1485789600,"sys":{"type":1,"id":5091,"message":0.0103,"country":"GB","sunrise":1485762037,"sunset":1485794875},"id":2643743,"name":"London","cod":200}

However if we wanted to unit test this class, we’ll quickly find out that we will be making an API call to the OpenWeatherApi every single time we run our test suite.

One way to mitigate this problem is by mocking the GuzzleHttp Client and returning the response.

However, because the GuzzleClient is initialized in the constructor we’re unable to mock it.

The Solution

In general, the less your classes know to function the better. This lesson touches on the concept of the Single Responsiblity Principle which basically states:

A class should be good at one thing.

Our Forecaster class is doing a so-so job at doing 2 things:

Create and store a GuzzleHttp client with a specific base URL

Send an API request with the stored GuzzleHttp client and return the decoded JSON results

Our Forecaster class should only be concerned with weather things right? Intializing and configuring an HTTP client shouldn’t be one of them. The creation and configuration of dependencies is were Inversion of Control Containers shine.

Refactoring Using an Inversion of Control Container

All major PHP frameworks include an IoC Container. It’s not a construct of the language but it’s a pattern that’s used to help us separate the creation and configuration of dependencies.

Whew that’s a lot of big words so I’ll just show you with a reasonable example.

Because we’re not swearing to any PHP framework in this book - I’ll turn to some of the highest quality PHP packages available. I’m speaking of the League of Extraordinary Packages.

These packages are awesome because they’re held to a very high standard. A few of the requirements to have a package listed:

Write unit tests. Aim for at least 80% coverage in version 1.

Use Semantic Versioning to manage version numbers.

Use a vendor namespace (League in our case) for PSR-4 autoloading. Shove code in a src folder.

One such package in this repository includes a package simply called Container

The Container package allows us to register and retrieve services:

$container=newLeague\Container\Container;// add a service to the container$container->add('service','Acme\Service\SomeService');// retrieve the service from the container$service=$container->get('service');var_dump($serviceinstanceofAcme\Service\SomeService);// true

Think of IoC Containers as just simple key => value stores. We store a class or Closure in the container and assign it a name so we can retrieve it later.

The benefits are threefold:

Easier unit testing because we can actually mock dependencies

Easier composition of classes

Code reuse becomes possible

This will become apparent when we refactor our Forecaster

Injecting the GuzzleHttp Client as a Dependency

To refactor our Forecaster class to know less and still be able to function as expected, we’ll move the creation of the GuzzleHttp Client outside of the class.

First thing’s first, let’s pass the GuzzleHttp client as an constructor argument:

Now, our Forecaster has no idea what $guzzle is, it only knows that there is a get() method defined on it. Congratulations! We can now unit test our Forecaster class because we can pass in a mocked version of Guzzle:

That’s just inefficent and not DRY. In the next section I’ll show you the final piece to understanding how to use the IoC Container to clean up this mess.

How to use an IoC Container to Leverage Dependency Injection

Now for the final piece - using a real IoC Container to abstract away the dependency injection required so we can unit test our PHP code.

Whew say that 5 times fast. Anyway, it’s a lot of terminology but I assure you it’s not as complicated as it sounds.

As you read before, the IoC Container is just a key => store container. You can think of it as an array of services. A service is just a piece of functionality that your application will use to perform some business logic.

In this example we’ll turn our WeatherApi into a service stored in a Container.

Let’s leverage the Container’s FactoryClosures which act identically to Laravel’s IoC service definitions.

// let's create our container:$container=newLeague\Container\Container;// now let's add a weather_api service that encapsulates how to create and configure a WeatherApi instance:$container->add('forecaster',function(){$guzzle=newGuzzleHttp\Client(['base_uri'=>'https://api.openweathermap.org/data/2.5/weather',]);$api=newForecaster($guzzle);});// now we can retrieve our WeatherApi anywhere!$forcaster=$container->get('forecaster');$weather=$forecaster->weatherIn('Billings, MT');

IoC Containers in Ruby

Welp, I hate to break it to you. IoC Containers aren’t really practiced in Ruby. I tried searching and searching for a definitive reason why, it bothered me for the longest time.

Because of the rigidness of static languages like PHP and Java, the IoC is pivotal. Without it we we would have much more difficulty unit testing our code.

Let’s start with a Ruby-ified version of our first naive Forecaster implementation, without the Dependency Injection.

First major difference is that Ruby doesn’t have a GuzzleHttp gem persay. They have a few swell HTTP Clients. For simplicity I’ll use the rest-client gem.

In PHP, we were forced to pass the HTTP client as an argument to the constructor. But in Ruby we can just override that particular constructor:

# Creating a mock "double" which is essentially the same thing as a Mockery::mock() objectmock=double(RestClient::Resource)# set the exceptation that the RestClient::Resource will be initialized and override the returning valueallow(RestClient::Resource).to(receive(:new).with('https://api.openweathermap.org/data/2.5/weather').and_return(mock))

Volia, we have mocked RestClient::Resource without any special Container needed.