Chapter 7. Testing RESTful Services

Testing RESTful services can be a difficult task that goes beyond just testing pieces of code. A service is in fact composed of different components, and in order to test its functionality, the interactions between these components need to be tested.

Most of the time, when testing techniques are introduced, we learn how to test if a given method actually returns the expected results for a given input or set of inputs.

Testing services requires instead that we test how our service interacts with Amazon AWS, or with external APIs, or with our own load balancer. This includes testing testing how all other services interacting with our APIs react to certain error codes or specific responses.

Testing in Rails

We learned in Chapter 2 that the Rails framework was built to allow test–driven development (TDD) from the beginning. Rails in fact creates a test folder as soon as a new Rails project is created. If you list the contents of this folder you will find that a different subfolder is created for each component of the application, so that tests can be structured accordingly:

$ ls test
controllers    fixtures    helpers        integration    
mailers        models      test_helper.rb

Testing models in a Rails app usually means unit testing. With unit testing the smallest testable parts of your Rails application—the models, in this case—are tested individually and independently, to ensure that the defined methods work as expected.

Functional testing tests the controllers.  The controllers handle the incoming requests and pass them to the various components of your service. When you are testing controller logic you are in fact testing the following:

  • Was the request successful?
  • Were the right HTTP code and message returned?
  • In what case does the resource return an error?
  • Which error does the resource return?
  • If an action is initiated or a resource is requested, is the right response returned to the client?

Let’s go back to our LocalPic application from the previous chapter. Imagine that we have a simple controller with an action that just returns the status of our service—i.e., if we request a certain resource and get a 200 response, we know the service is up and running.

This controller creates a status endpoint that can be used by monitoring services to periodically check on our LocalPic API. If our API goes offline for any reason, the monitoring service will send a notification.

Let’s generate a simple status controller:

$ rails g controller api/status

And then the actual code:

class API::StatusController < ApplicationController
  def index
    render json: { alive: true }
  end
end

And the test for it:

require 'test_helper'
class API::StatusControllerTest < ActionController::TestCase
  test 'status should be fine' do
    get :index
    assert_response :success
    assert_equal '{"alive":true}', @response.body
  end
end

Of course this is a simple example, but the logic applied to test controllers follows the same pattern: if we request a certain resource, do we receive the expected response?

We’ll continue by testing the actions on the pictures controller.

To test the index action we simply need to add the following test to the pictures_controller_test.rb file in test/controllers:

test "should get index" do
  get :index
  assert_response :success
end

Please note that this will only assert that the index action returns success. It will not assert what data the action actually produces.

Now we continue with the show action:

test "should get show" do
  get :show, id: 1
  assert_response :success
end

Note that in this action we are passing the id parameter. We therefore need to add it to our pictures.yml fixtures file.

We edit that file as follows:

one:
  id: 1
  title: "Test title"

Tests for the other controller actions can be found in the repository for the LocalPic application. I encourage you to try to write the tests yourself before looking at the repo for the solution. Writing tests is a great exercise to understand how our applications really work and to make sure that our code remains stable even when we change things.

Mocks, Stubs, Doubles, and Dummies

When you start testing RoR applications, and especially their interactions with different services, you will start hearing about “stubbing” and “mocking” certain things. But what exactly are mocks, and what are stubs? The difference is very subtle and often confusing.

Mocks Aren’t Stubs

In 2007, Martin Fowler from ThoughtWorks published a famous blog post called “Mocks Aren’t Stubs”.

A mock object is an unreal object that we create for testing purposes. We use this when we do not want to or cannot use real data in the test.

Mocks verify behavior. The mock object is told what to expect during the setup phase of the test. During the verification phase of the test, we ask the mock to verify itself.

You’ll soon discover there are different terms to describe things that seem to overlap, at least in part. You’ll hear about test doubles, dummies, fakes, stubs, and mocks.

Let’s start from the beginning:

  • Double is a general term to describe any not-real object used for the purpose of testing.
  • Dummy objects are usually only used to fill parameter lists; they are not really used for actual testing.
  • Fake objects can be used for testing as they possess some implementation of the actual objects they represent.
  • Stubs are used to provide canned responses to calls made during the test but do not respond to anything outside of the test’s scope. A good example of how and where we can use a stub is when we want to test how our application interacts with some external service, without having to call it directly. We stub the service so that it behaves exactly like it and answers to the same requests.
  • Mocks are objects that are preprogrammed with certain expectations and follow the specifications of the real object.

Mocks are not like fixtures. Fixtures are only fake database records.

Testing RESTful Services

Testing RESTful services means integration testing. A service must interact with the outside environment through the APIs it exposes and the external APIs it integrates.

Now, in a perfect world, all services you decide to integrate—whether internal to your organization or external—would always expose the same APIs, or at least notify their users if they were about to change something with regard to what data they return or how to make certain requests.

In the real world, this is hardly the case.

Integration tests have to take this very important aspect into consideration. You’ve probably designed some portions of your application in a way that integrates perfectly with one or more services that you have chosen to or had to integrate. What happens if one day these services change, and some of your app’s functionality does not work as expected?

A good idea to ensure that your integrations are stable is to test that each service is responding as expected. This means, in a continuous integration environment, that you write tests that are run every time your production environment is tested. This way you will be able to spot changes right away, hopefully before your users notice.

Imagine that you are integrating a payment service. You can write tests to ensure that you are always sending requests to the service in the right format. If something in the APIs changes, ideally your tests will notice it and raise an error or simply fail.

There are also tools that take care of things like this for you. These tools work like a pager for the APIs you want to monitor, and when something changes they send you a verification message.

Wrapping Up

In this chapter we took a quick look at testing, stubbing, and mocking RESTful services and APIs. We saw how we can test interactions between our app and the external services that will interact with our models and controllers. In the next chapter we will start talking about SOA practices applied to microservices and microapplications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.47.218