Search code examples
rubyrspecsinatrarack

Testing a rack service that relies on another rack service


I am re-factoring a medium-sized web application which has many unit tests and acceptance tests written using RSpec. My main goal is to clean up code for a microservice that runs as a separate rack application in production. The current test suite uses process management to start and stop the microservice, which is causing problems with test performance and complicating how the test environment manages test fixture data.

There are hundreds of tests that rely on the correct behaviour of the microservice, which means on this re-factoring pass I will not get the opportunity to fully mock out or fake the microservice itself. But I would really like to remove all the separate process-handling and the associated costs. This is now feasible since I am adding unit tests for the microservice itself, and we have a separate smoke and integration test infrastructure which already runs with the microservice handled as a separate rack-based service.

I have created a SSCE to demonstrate the problem. It is in multiple small files, fully functional and includes tests. Apologies for the overall length, but I think it is unavoidable because my question is about how to combine these multiple components.

Microservice

microservice/app.rb

require "grape"

class Microservice < Grape::API
  format      :json
  get '/main' do
    { :message => "Hello World!" }
  end
end

microservice/config.ru

require File.join(File.dirname(__FILE__), "app")
run Microservice

microservice/spec/spec_helper.rb

require_relative '../app.rb'

require 'rspec'
require 'rack/test'

def app
  Microservice
end

RSpec.configure do |config|
  config.include Rack::Test::Methods
end

microservice/spec/microservice_spec.rb

require_relative 'spec_helper'

describe Microservice do
  describe "GET /main" do
    it "responds with correct JSON" do
      get "/main"
      expect( last_response ).to be_ok
      data = JSON.parse( last_response.body )
      expect( data ).to be == { "message" => "Hello World!" }
    end
  end
end

Main Web Application

webapp/app.rb

require 'sinatra'
require 'json'
require 'httparty'

# This stands in for more complex config we have in reality
$microservice_url = 'http://127.0.0.1:8090/'

get '/main' do
  # Calls a microservice . . . (annoyingly the service uses the same route name)
  response = HTTParty.get( $microservice_url + "main" )
  data = JSON.parse( response.body )
  "#{data['message']}\n"
end

webapp/spec/spec_helper.rb

require_relative '../app.rb'

require 'rspec'
require 'rack/test'

def app
  Sinatra::Application
end

RSpec.configure do |config|
  config.include Rack::Test::Methods
end

# Is there something I can do here to load the
# microservice without launching it in a new process, and route
# the HTTParty.get in the main app to it?

$microservice_url = 'http://127.0.0.1:8888/'

webapp/spec/webapp_spec.rb

require_relative 'spec_helper'

describe "Main web app" do
  describe "GET /main" do
    it "responds with correct text" do
      get "/main"
      expect( last_response ).to be_ok
      expect( last_response.body ).to include "Hello World!"
    end
  end
end

I can run the microservice test easily:

rspec -f d -c microservice/spec/microservice_spec.rb

But to run the webapp test, I first need to start up the microservice where the test expects to find it:

rackup -p 8888

(Different process)

rspec -f d -c webapp/spec/webapp_spec.rb

I think I can mount the microservice inside the application, from viewing questions like How to mount a Sinatra application inside another Sinatra app? but this seems geared to joining applications in a production environment, I need them to be separate there, joined only in unit tests where the helper enables it, and so far am at a complete loss on how I tell HTTParty (or any replacememt which could do what I want here) to connect.


Here's how I might stub the single call in the example (at end of webapp/spec/spec_helper.rb) - is there a way to route this to an in-process mount of the microservice instead?

require 'webmock/rspec'
include WebMock::API
stub_request( :any, /:8888\// ).to_return( :body => '{ "message":"Hello World!"}' )

Solution

  • WebMock provides routing to a Rack response, so package microservice so it's available to webapp and then you can:

    require 'microservice'
    stub_request( :any, /:8888\// ).to_rack( Microservice )