Automating testing is one of the most important aspects to building trustworthy applications. Testing can seem like a tedious process, but your test suite becomes the bedrock of your development process. It’s an investment to devote write tests for a feature, but it pays in dividends when change inevitably happens.

Testing Forecaster in PHP

In this section, I’ll keep things simple and show you how to unit test a class in PHPUnit, then show you the Ruby equivalent with RSpec.

Let’s start with our OpenWeather API wrapper we made in the IoC Container example a few sections ago:

<?php
// src/Forecaster.php

namespace PHPtoRuby;

use GuzzleHttp\Client;

/**
 * Forecaster 
 *
 * @description An API client for the OpenWeather API
 */
class Forecaster {

  /**
   * @var GuzzleHttp\Client
   */
  private $guzzle;

  /**
   * Constructor
   *
   * @param GuzzleHttp\Client
   */
  public function __construct(Client $guzzle) {
    $this->guzzle = $guzzle;
  }

  /**
   * Find the weather in a particular city
   *
   * @param Mixed
   * @return stdClass
   */
  public function weatherIn($city) {
    $response = $this->guzzle->get([
      'query' => [
        'q' => $city,
        'appId' => 'INSERT YOUR APP ID HERE',
      ]
    ]);


    return json_decode($response->getBody()->getContents());
  }
}

This is our lovely weather forecasting class. We’ll be mocking the GuzzleHttp\Client from the GuzzleHttp package.

Setting up PHPUnit

PHPUnit is the most widely used PHP testing library. Codeception is a great alternative but since you most likely use PHPUnit I’ll keep my example test in that suite.

First of all, we need to install PHPUnit into our project:


    composer require --dev phpunit/phpunit

Composer will now install PHPUnit as a development dependency. We don’t run our test suite in production so it’s fine to be in the require-dev entry of our depedencies:


// rest of composer.json
...
    "require-dev": {
        "phpunit/phpunit": "^7.1"
    }
...

Creating our Unit Test for Forecaster

We’ll need to create a separate file that describes our unit test for the Forecaster class we created for integrating with OpenWeather API.

Create a tests directory next to the src directory and create a new test file called tests/ForecasterTest.php. Here are the contents of ForecasterTest.php:

<?php

use PHPUnit\Framework\TestCase;
use GuzzleHttp\Client;
use PHPtoRuby\Forecaster;

class ForecasterTest extends TestCase
{
    public function testGuzzleWithReceiveCityArgument()
    {
        $city = 'Alliance, OH';

        $guzzle = $this->getMockBuilder(Client::class)
            ->setMethods(['get'])
            ->getMock();

        $guzzle->expects($this->once())
          ->method('get')
          ->with($this->assertArraySubset(['q' => $city]))
          ->will($this->returnValue($this->getMockedResponse()));

        $forecaster = new Forecaster($guzzle);

        $forecaster->weatherIn($city);
    }

    private function getMockedResponse() {
        $response = $this->getMockBuilder(\stdClass::class)
          ->setMethods(['getBody', 'getContents'])
          ->getMock();

        $response->expects($this->once())
          ->method('getBody')
          ->will($this->returnSelf());

        $response->expects($this->once())
          ->method('getContents')
          ->will($this->returnValue(json_encode([
            'data' => 'Weather data goes here'
          ])));

        return $response;
    }
}

Running PHPUnit Tests

To run the test, simply run this command in the root directory of the project:


    vendor/bin/phpunit tests

You should see that our 1 test passes.

The phpunit command will look for all tests located in the path you provide. In our case we passed the path to the tests directory. It found our test we created at tests/TestForecaster.php, executed the test and displayed the results in our terminal.

PHPUnit will treat every public method on a particular class extending the PHPUnit\Framework\TestCase.

Note: if you see an error about vendor/bin/phpunit does not exist make sure you’re in the right directory and make sure you have PHPUnit installed via composer.

What’s going on in the Forecaster Unit Test?

Whew! There’s a lot going on in our test example. We’re diving straight into mocking classes. You may have noticed there are 2 mocks in our example.

The first mock is straightforward, we need to mock Guzzle itself so we don’t actually make the API call to OpenWeather API:

        $guzzle = $this->getMockBuilder(Client::class)
            ->setMethods(['get'])
            ->getMock();

Now $mock is a mock of the GuzzleHttp\Client that Forecaster uses to send the API request.

Guzzle’s Response class uses a fluent interface:

  // $response is a GuzzleHttp Response
  $response->getBody()->getContents();

  get_class($response);
  => 'stdClass'

That’s why we had to create a second mock that will allow us to return the JSON string we need to complete the test.

However here is the real meat and potatoes of the test:

        $guzzle->expects($this->once())
          ->method('get')
          ->with($this->assertArraySubset(['q' => $city]))
          ->will($this->returnValue($this->getMockedResponse()));

Here we’re testing to make sure that our Guzzle Client recieves the get() call with the $city variable assigned to the q key in the array.

Now we know for certain whatever we pass to the $forecaster->weatherIn($city) it will be assigned to the q query parameter that the OpenWeather API expects.

If you wanted to test different responses from the API, you’d change the response mock and have it return different messages from the API.

You can also mock API responses by recording them. Check out the php-vcr package for more information on how to use it.

I’ve found that approach to be useful when testing successful responses, but I tend to mock the error responses you receive from APIs.

Testing in Ruby

I have written unit tests in three different languages: PHP, Javascript and Ruby. As much as I love Codeception for PHP, Ruby has been by far the most blissful testing experience.

Thanks to the dynamic nature of Ruby, it’s very flexible and easy to unit test code - even code that has bad practices like object instantiation in the constructor.

Ruby has a few test suites available, such as Minitest and RSpec.

In this series I’ll be showing how to write unit tests in Ruby with RSpec.

Installing RSpec

RSpec is just a gem, much like PHPUnit is a package. To install it in our Forecaster Ruby project we need to add it as a dependency for our test group:


  bundle add rspec --group test

Note:* a “group” in a Gemfile is like defining an environment. In Composer we only have 2 groups available - “dependencies” which are for production and “dev-dependencies” which are used for development and testing. Bundler has several groups, such as “production”, “development” and “test”.

If all went well, then the contents of your Gemfile should resemble this:


# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# gem "rails"

# Added at 2018-05-27 13:56:40 -0400 by dylanpierce:
gem "rspec", "~> 3.7", :group => [:test]

Intializing RSpec

After RSpec is installed in the Gemfile, we need to intialize it. Simply run this command in your terminal:


rspec --init

This will create 2 files: spec/spec_helper.rb and .rspec. In each of your specs you’ll need to require the spec_helper.rb. Think of it as the RSpec settings file.

Our Subject Class

Our “subject” is the class we’re going to test. Let’s reuse our Forecaster class from the IoC Container section:


require 'json'

class Forecaster
  attr_accessor :client

  def initialize
    @client = RestClient::Resource.new('https://api.openweathermap.org/data/2.5/weather')
  end

  def weather_in(city)
    response = @client.get(params: { 
                                     q: city, 
                                     appId: 'INSERT YOUR APP ID HERE'
                                   })

    JSON.parse(response)
  end
end

This class is an API wrapper around the OpenWeather API just like our PHP example from above.

Creating your First RSpec Unit Test

Awesome, it’s time to write your first Ruby unit test. It’s exciting stuff!

One nominclature difference between RSpec and other libraries, in RSpec terminology individual test cases are called specs.

So we always suffix our test files with _spec.rb.

In addition, the directory your individual specs live in is also called spec, it should have been created when you ran rspec --init.

Inside of this spec directory, create a new file called forecaster_spec.rb:


require 'spec_helper'
require 'json'
require_relative '../forecaster'

RSpec.describe Forecaster do
  it 'passes the given city as an argument to the API' do
    city = 'Alliance, OH'

    client = double(RestClient::Resource)
    allow(RestClient::Resource).to receive(:new).and_return(client)

    expect(client).to(receive(:get)
                  .with({params: { q: city, appId: 'INSERT YOUR APP ID HERE'}})
                  .and_return(JSON.generate(data: 'weather data here')))

    forecaster = Forecaster.new
    forecaster.weather_in(city)
  end
end

Running Specs

Running specs is a breeze:


rspec

You should see that our 1 spec has passed! Woo!

Note: since spec is the default directory, you just need to run the rspec executable that was installed when you added it to your Gemfile.

What’s going on in our Spec?

So the whole point of this test was to verify that the city argument to forecaster.weather_in(city) would be passed to the OpenWeather API as q in the HTTP query.

Let’s take this spec apart and see what’s going on starting with the first line that defines the spec:


RSpec.describe Forecaster do
  # ...contents of spec inside of the block
end

We start off a spec as you can see by using Rspec.describe(Class). This describe method accepts a block and executes it.

It’s a little different from PHPUnit’s Class definition. Instead of inferring that all public methods in a class extending TestCase are tests, we specify them with it blocks:


RSpec.describe Forecaster do
  it 'will run this block as a test' do
    # ...contents of spec inside of the block
  end
end

This it block will be executed as a single test. It leads to some very readable tests which is a huge benefit to your future self.

Asserting things

In PHPUnit, we’re used to making assertions as a method of the object:

$this->assertTrue(true);
$this->assertFalse(1 == 2);
$this->assertEquals('hello', 'hello');
// etc...

In RSpec, we don’t call them assertions - we call them matchers.

Matchers are what we expect arguments or results to look like. In PHP we’re used to this kind of format:

// 1. Assertion
$this->assertEquals(
  // 2. Subject
  'hello', 
  // 3. Compared to
  'hello'
);

However in Ruby:


# 1. Subject
expect('hello')
  .to(
      # 2. Assertion
      eq(
        # 3. Comparison
        'hello'
      ))

Shortened to how you’d normally see these “assertions” a.k.a. “expects” in RSpec:


expect(true).to be true
expect(1 == 2).to be false
expect('hello').to eq 'hello'

It’s amazing how close to human language it gets.

Back to our test, so far we have an basic outline of a test, but we need to start mocking our RestClient that’s making the API call to OpenWeather:


RSpec.describe Forecaster do
  it 'will run this block as a test' do
    # Our test city, my beautiful hometown
    city = 'Alliance, OH'
    # Our mock, a.k.a. a double
    client = double(RestClient::Resource)
  end
end

So we’re in the Arrange phase of testing our Forecaster.weatherIn(city) method. We’ve just arranged our 2 variables - the city and the mock of the API client.

Mocks in RSpec

As you’ve noticed, you don’t need to define methods initially like PHPUnit’s Mocking Library.

RSpec has a few ways of mocking objects, here we’ll use a basic “double”. A double is very much like a mock.

The constructor argument is not actually required, but it’s helpful to pass the class of what you’re mocking to a double so it’s easier to read. You can create a double with just a string or pass nothing if you’d like:


# this will work perfectly fine
mock = double

# this will also work perfectly fine
bird = double('mocking jay')

In our case we explictly passed the Class to the constructor of the double:

client = double(RestClient::Resource)

Stubbing vs Expecting on RSpec Mocks

This part was very confusing to me at first when I was learning RSpec testing. In PHPUnit and mocking libraries like Mockery, we don’t have this concept of allow vs expect.

allow allows you to stub a method.

So we know that our client instance’s get method will eventually be called with our parameters to the OpenWeather API:


# snippet from forecaster.rb

  def weather_in(city)
    # We are going to mock this @client's .get method
    response = @client.get(params: { 
                                     q: city, 
                                     appId: 'INSERT YOUR APP ID HERE'
                                   })

    JSON.parse(response)
  end

Say we didn’t want to set an expectation on this .get method and instead just wanted to have it return something. In that case we’d set an allow on the mock:


client = double(RestClient::Resource)

allow(client).to(receive(:get))

In layman’s terms: allow the client API double to receive a message to the get method.

But the next line says we’re going to parse the response from this stub as if it was JSON. We need it to return a string of JSON.

Let’s use .and_return to specify the response to our message on the mocked get method:


client = double(RestClient::Resource)
json_response = JSON.generate(data: 'weather data here')

allow(client).to(receive(:get).and_return(json_response))

The parantheses on this statement may throw you off at first, I’ll show it again without the wrapping paranthesis because they’re optional and can be inferred by Ruby:


allow(client).to receive(:get).and_return json_response

So in laymans terms again: allow the client API double to receive a message to the get method and return a JSON string as a response.

expect sets an expectation that a particular method will be called on double.

You can think of expectations as assertions. We’re essentially asserting that a method will be called on a double.

So let’s expect that the .get method will be called on our client double:


client = double(RestClient::Resource)
json_response = JSON.generate(data: 'weather data here')

expect(client).to(receive(:get).and_return(json_response))

Using Matchers to Assert Given Arguments

Our expectation is asserting that the client does receive get. However it doesn’t check what is being passed to the get method.

We need to set expections on what @client.get is being called with.

In our actual Forecaster class, we pass a multidimensional hash:


    response = @client.get(params: { 
                                     q: city, 
                                     appId: 'INSERT YOUR APP ID HERE'
                                   })

What we care about in this method is that that hash has the city passed to it in the correct location.

For this we’ll use the .with expectation modifier:


    params = { params: { q: city, appId: 'INSERT YOUR APP ID HERE' }}
    expect(client).to(receive(:get)
                  .with(params)
                  .and_return(json_response))

In laymans terms: we expect the client to receive a message to the get method with a hash that contains params.

Now if we were to run our full test:


RSpec.describe Forecaster do
  it 'passes the given city as an argument to the API' do
    ### Arrange
    # Our test city, my beautiful hometown
    city = 'Alliance, OH'

    # Our mock, a.k.a. a double
    client = double(RestClient::Resource)

    # The parameters we expect to be given to the API client's .get method
    params = { params: { q: city, appId: 'INSERT YOUR APP ID HERE' }}

    # our mocked OpenWeather API response
    json_response = JSON.generate(data: 'weather data here')

    ### Assert
    # expect what our mocked API client will receive .get
    expect(client).to(receive(:get)
                  .with(params)
                  .and_return(json_response))

    ### Act
    # create a Forecaster instance and perform the action
    forecast = Forecaster.new
    forecast.weather_in(city)
  end
end

If we were to run our test with rspec we’d get:


Expected <Double RestClient::Resource> to recieve :get exactly 1 times but received it 0 times

This is because we have instantiated the RestClient::Resource and assigned it to @client in the constructor of Forecaster:


  # snippet from forecaster.rb:
  def initialize
    @client = RestClient::Resource.new('https://api.openweathermap.org/data/2.5/weather')
  end

Oh No! We made this mistake in PHP and we had to refactor our code in order to test for this case. It was impossible for us to inject our mock when the object was instantiated in our subject’s code.

Well, Ruby is different.

Believe it or not, we don’t have to refactor our original code to test this. Remember that everything is an object and that even Classes are objects. Therefore they are malleable.

Overriding .new in Ruby

We know that the .initialize method is a object level method. Therefore the .new method that we call is actually as a class level method.

We can stub any method we please, including .new:


# Our mock, a.k.a. a double
client = double(RestClient::Resource)

# stub the RestClient::Resouce's .new method and return our mock instead
allow(RestClient::Resource).to receive(:new).and_return(client)

Just like that, now whenever our RestClient::Resource creates a new instance, it will instead return our double.

It’s so easy to test all code in Ruby because of it’s flexibility. PHP’s requirement of passing dependecies via a constructor or setter argument in order to test encourages composed DRY code.

But not all PHP code is written with this in mind, this creates code that is sometimes impossible to unit test.

In Ruby, overriding a dependency instantiated in a constructor or even an instance or class method becomes trivial. You can refactor your code later, but at least you can test legacy code no matter the state it’s in.

Completing the Test

Now we have all the individual pieces we need to finish this unit test the Forecaster.weather_in(city) method:


require 'spec_helper'
require 'json'
require_relative '../forecaster'

RSpec.describe Forecaster do
  it 'passes the given city as an argument to the API' do
    ### Arrange
    # Our test city, my beautiful hometown
    city = 'Alliance, OH'

    # Our mock, a.k.a. a double
    client = double(RestClient::Resource)

    # The parameters we expect to be given to the API client's .get method
    params = { params: { q: city, appId: 'INSERT YOUR APP ID HERE' }}

    # our mocked OpenWeather API response
    json_response = JSON.generate(data: 'weather data here')

    # override the RestClient::Resource.new method to return our double instead of a real instance
    allow(RestClient::Resource).to receive(:new).and_return client

    ### Assert
    # expect what our mocked API client will receive .get
    expect(client).to(receive(:get)
                  .with(params)
                  .and_return(json_response))

    ### Act
    # create a Forecaster instance and perform the action
    forecast = Forecaster.new
    forecast.weather_in(city)
  end
end

You’ve just learned all you need to know about how to unit test in Ruby. That’s no small feat. There are all sorts of RSpec concepts to learn after this, but they’re just productivity enchancers.

With this knowledge, you’re able to test at least 83% of use cases you’ll come across in Ruby code.