giant robots smashing into other giant robots

Written by thoughtbot

hrward

How to test Sinatra based Web Services

Sinatra is a fantastic lightweight framework for building web services. We’ll use it as the application framework for the HTTP endpoints in our Service Oriented Architecture.

The testing approach will be in-process, which means that the test suite is running in the same Ruby process as the web service. This eliminates the need to run an external HTTP web server.

Application structure

Unlike Rails, Sinatra isn’t all that opinionated on how you set up your application (it has a few sensible defaults), which can lead to a lot of open questions on how to structure the application.

Here’s the directory structure we’ll use for our example application.

app/
app/models
app/my_service.rb
client/
client/lib/my_client.rb
client/my_client.gemspec
config/
spec/

Outside-in development with a Client Gem

For internal services we’ll build a client gem directly into the project. With the client embedded in our codebase we can follow outside-in development cycles — Features start with request specs from the client side, followed by the addition of end points to the service, and then unit tests within the application.

This allows the us to dogfood our client gem and bring it in as the first step of our development process.

To achieve this we need to configure a few things.

1. Set up the project gemfile to use a local copy of the client in test mode

Include the local client gem in test suite.

# Gemfile
group :test do
  gem 'my_client', path: 'client'
  gem 'webmock'
  # ...
end

2. Use webmock to send all http requests to the service

Instead of booting up a web server every time the test suite is run we’ll mount the Sinatra service as a rack application with webmock.

This allows the client to talk directly to the mounted rack application without going through HTTP or a web server.

# spec/spec_helper.rb
RSpec.configure do |config|
  config.include WebMock::API

  config.before(:each) do
    MyClient.base_url = 'http://www.example.com'
    stub_request(:any, /www.example.com/).to_rack(MyService)
  end
end

3. Use the client in our request specs

Once MyService is mounted as a rack application we can use the client gem directly in our test suite.

# spec/requests/widget_management_spec.rb
require 'spec_helper'

describe "Widget management" do
  it "creates a Widget" do
    # set up fixture data if needed

    response = MyClient::Widget.create(widget_params)

    # assert expectations on the response
  end
end

Private gem hosting

To use the client gem in other projects we can use a private gem hosting service like Gemfury. This allows us to include the client via gemfile in our other projects.

# Gemfile
source 'https://452f6E403CDph10714e41@gem.fury.io/me/'
gem 'my_client'

source 'https://rubygems.org
# ...

Takeaways

  • Sinatra is great framework for creating lightweight web services.
  • Webmock allows us to test the client in-process against a rack application.
  • Use private gem hosting to distribute the shared client.

Written by Harlow Ward

dancroak

Foreman as process manager, Pow as DNS server and HTTP proxy

Web app development/production parity can be improved by defining process types in a manifest named Procfile such as this one for a Rails app:

web: bundle exec rails server thin -p $PORT -e $RACK_ENV

In production, Heroku’s Cedar stack reads process types from that file.

In development, Foreman manages output streams, responds to crashed processes, and handles user-initiated restarts and shutdowns.

One downside to Foreman is typing http://localhost:3000, http://localhost:3100, etc. into the web browser, incrementing the port number for each app, forgetting which port is which app.

Pow solves this elegantly and is easy to install and maintain.

Pow has a DNS server, capable of port proxying to Rack apps.

Install and run Pow:

curl get.pow.cx | sh

Configure Foreman to always use the same port:

cd /path/to/myapp
echo 'port: 7000" > .foreman

Relate the port to Pow:

echo 7000 > ~/.pow/`basename $PWD`

Use Foreman normally:

foreman start

Reap the benefits of well-named DNS like http://myapp.dev.

It’s possible Pow may see improved, or automatic, Foreman compatibility. Watch this pull request for updates.

Written by .

dancroak

Writing an API client with test, staging, and production in mind

I was recently tasked with this story:

Background:
  Given I go to the sign up page
  And I fill in "Email" with "new@example.com"
  And I fill in "Password" with "password"

Scenario: Visitor signs up and does not want email
  When I press "Sign up"
  Then email service is not notified "new@example.com" signed up

Scenario: Visitor signs up and wants email
  When I check "Please email me"
  And I press "Sign up"
  Then email service is notified "new@example.com" signed up

The email service in question was internal, accessible via an HTTP API, and did not have a Ruby client library.

So, I had to write and test my own. The usual approach would involve using a HTTP stubbing library like Sham Rack or Artifice. I decided to try something different and see how it felt.

First, I needed an interface that I could test:

Then /^email service is notified "([^"]*)" signed up$/ do |email|
  EmailService.notifications.should include(email)
end

Then /^email service is not notified "([^"]*)" signed up$/ do |email|
  EmailService.notifications.should_not include(email)
end

I’m re-using a pattern (“store data in a simple array for easy state-based testing”) we’ve used before for Javascript integration testing Mixpanel.

So I have the start to an EmailService interface. I thought I wanted it invoked as part of an after_save callback on the User model, so here’s that spec:

describe User, 'who opts into email' do
  subject { build(:user, email_opt_in: true) }

  before do
    EmailService.stubs(:notify)
    subject.save
  end

  it 'notifies EmailService' do
    EmailService.should have_received(:notify).with(subject.email)
  end
end

describe User, 'who does not opt into email' do
  subject { build(:user, email_opt_in: false) }

  before do
    EmailService.stubs(:notify)
    subject.save
  end

  it 'does not notify EmailService' do
    EmailService.should have_received(:notify).never
  end
end

This is the stubbing and spying technique and uses RSpec, mocha, and bourne.

My thought process was that I needed an active verb, notify to invoke when the user is created, but that will store the invocation in a notifications array that the Cucumber step definition needs to check state (I don’t want to stub, spy, or mock in an integration test).

So, making the user spec pass isn’t bad:

require 'email_service/notifier'

class User < ActiveRecord::Base
  after_create do
    if email_opt_in?
      EmailService.notify(email)
    end
  end
end

Now, the EmailService can be spec’ed. We already had John Nunemaker’s HTTParty as a dependency in the app, and I only had to make one HTTP POST, so I knew I would be re-using HTTParty’s interface.

describe EmailService::Notifier, '#post' do
  subject { EmailService::Notifier }

  let(:email) { 'new-signup@example.com' }

  before do
    subject.stubs(:post)
    subject.new(email).post
  end

  it 'POSTs to email service with API_KEY and given email' do
    subject.should have_received(:post).with(
      subject::URL, query: { api_key: subject::PARAMS, email: email }
    )
  end
end

What I care about here is that during the one POST the app has to make, that the parameters are correct. This is close to hitting the live service as I’m willing to get without making an HTTP request.

I had an internal debate with myself while writing it over whether this is “stubbing the system under test” (considered bad practice). I decided “no” because despite the subject being stubbed, the system under test is actually the EmailService::Notifier#post method.

The stubbed and spied method is also mixed in from HTTParty, so I feel clean with this approach.

Making it pass:

require 'httparty'

module EmailService
  class << self
    attr_accessor :notifications
  end

  def self.notify(email, live = false)
    if Rails.env.production? || live
      Notifier.new(email).post
    else
      self.notifications << email
    end
  end

  class Notifier
    include HTTParty

    API_KEY = 12345
    URL = 'http://emailservice.example.com'

    def initialize(email)
      @url   = URL
      @email = email
    end

    def post
      self.class.post(@url, query: { api_key: API_KEY, email: @email })
    end
  end
end

Summary

  • I’ve got an environment-specific conditional inside the true public interface for the service, EmailService.notify, which is used by the User model.
  • In my integration test and in the development environment, all that happens is an array is populated, which makes it easy to confirm that it was notified correctly.
  • I’ve got a unit test that makes sure the correct URL and API key are used.
  • I’ve got a way to override EmailService.notify with the live flag so I can invoke it from the Rails console on production or staging when testing or debugging.

This almost takes longer to describe than to code but I’m curious what people think about this style of writing an API client with test, staging, and production environments in mind. How do you do things differently?

Written by .

dancroak

Do-it-yourself @font-face web service

Phil recently designed Developers Developers Developers Developers, an event we’re running for Boston-area high school and college students:

image

It looks awesome in part because of liberal use of @font-face.

These days, it’s simple to use @font-face. Just use a stylesheet referencing a web service like Google Font Directory:

 <link href='http://fonts.googleapis.com/css?family=Lobster&subset=latin' rel='stylesheet' type='text/css'>

… then use the font as part of a normal CSS font stack:

#schedule .event-time {
  font-family: 'Lobster', arial, sans-serif;
}

What if the font isn’t on an @font-face web service?

There are plenty of commercial @font-face web services, which Kyle has covered in detail.

However, there are cases where the font you want to use isn’t on an existing service. Or, the font you want to use requires explicit permission from the font’s creator before you can use it.

We ran into this problem on Developers Developers Developers Developers.

First attempt: serve fonts from the app

To solve it, we included the font files in our git repository. This worked, but wasn’t ideal:

  • Unnecessarily adds large files to the repo.
  • No HTTP caching by default, downloading font files on subsequent requests.
  • Repetition. Soon after, I wanted to use League Gothic again on another app.

Second attempt: serve fonts directly from Amazon S3

So our next attempt was to upload the @font-face stylesheet and font files to S3 and serve them directly from there like:

<link href='http://our-bucket.s3.amazonaws.com' type='text/css'>

At first, this seemed to work well. We could set some HTTP headers to handle caching and everything seemed like it was in the right place:

  • application code in version control
  • assets in an asset host (S3)

However, Firefox (and probably some versions of IE), balks at this technique because of the same origin policy.

On S3, you’re not allowed to tweak the Access-Control-Allow-Origin HTTP header to allow Firefox to serve your font from S3. There’s a long Amazon thread where S3 customers are asking for this ability.

However, even if you could set the Access-Control-Allow-Origin header on S3, you also want the Content-Type, Cache-Control, and Expires to all also be set in a standard way. It’s a pain to have to do that manually.

Solution: serve fonts from a DIY web service

Our final solution was a small Sinatra app called Fist Face. It is now open source under the MIT License and it solves all the problems we experienced.

Fist Face

Greatest boxer alive

It works exactly the same way as Google Font Directory, Typekit, or any other @font-face web service, except that you have full control over it:

<link href='http://your-font-face-service.com/league-gothic.css' rel='stylesheet' type='text/css'>

To use Fist Face, this is all you have to write:

# Gemfile
source 'http://rubygems.org'
gem 'sinatra',  '~> 1.1'
gem 'fistface', '~> 1.0'

# config.ru
require 'rubygems'
require 'bundler'
Bundler.require
run FistFace

Then deploy it. Follow a few conventions in the README regarding your asset host (ex: S3) and a few minutes later, you’ll be serving fonts via your own @font-face web service.

Why would I want to do it myself?

If you’ve run into the same issues that we did, have you solved this problem differently?

Among the weaknesses of this approach are:

  • No CDN. (patches welcome!)
  • Up to you to write a decent cross-browser CSS file.
  • The font files you use might be old-school (not “hinted” for computer screens).
  • Requires deeper knowledge of typefaces.

With an open mind, the last weakness is actually a strength. Typography is pretty interesting. You’ve got all these independent type foundries doing beautiful work and some release their fonts for free under permissive licensing.

In that way, hunting great typefaces for your @font-face web service is like building your own art collection. It can differentiate your work.

So, when your friends ask you, “whoa, what font is that?”, you can tell them, “you’ve probably never heard of it” … which I’ve heard is hip.

Written by .

lolconomy

A HTTP testing proxy

Hoptoadwhich is now live—is both an application and a Rails plugin that must work together. This integration simply cannot go untested in a test-happy place like thoughtbot.

Battletoads

The plugin, I’m sure you’ve seen, has a private method #send_to_hoptoad that handles the dirty HTTP stuff. It looks like a more complicated version of this:

def send_to_hoptoad(data)
  url = HoptoadNotifier.url
  Net::HTTP.start(url.host, url.port) do |http|
    headers = {
      'Content-type' => 'application/x-yaml',
      'Accept' => 'text/xml, application/xml'
    }
    response = begin
                 http.post(url.path, stringify_keys(data).to_yaml, headers)
              rescue TimeoutError => e
                 nil
               end
    case response
    when Net::HTTPSuccess then
      logger.info "Hoptoad Success"
    else
      logger.error "Hoptoad Failure"
    end
  end
end

Dead frog controlled via a network

The integration test simulates the plugin actually hitting the application. Normally to test #send_to_hoptoad you’d use Mocha to stub out Net::HTTP methods, but stubbing sweeps away too many potential issues here.

What we really want is an integration test that pits the plugin against the real application, without running a server. We want Net::HTTP#post to use ActionController::Integration::Session#post .

The gruesome internals

In the integration test for the application, first require in the needed tricks:

require 'test_helper'
require 'net/http'
require File.dirname(__FILE__) + '/../lib/hoptoad_notifier/lib/hoptoad_notifier'

(we’ve installed a copy of the plugin into test/lib)

Then, open up Net::HTTP and get rid of the bits that connect to the network. This part could be done with Mocha, but we need to open Net::HTTP later so we might as well do it this way:

class Net::HTTP < Net::Protocol
  def connect
  end
end

While you have Net::HTTP open, replace #post with a proxy. The class to proxy to is passed into the test_unit_class module variable.

class Net::HTTP < Net::Protocol
  mattr_accessor :proxy_object

  def post(path, body, headers)
    self.class.proxy_object.post path, body, headers
  end
end

Finally in the test setup block we need to initialize Net::HTTP with the appropriate instance of ActionController::Integration::Session (which is to say, self):

class PostingFromHoptoadNotifierTest < ActionController::IntegrationTest
  context "with a connection from the plugin to the application" do
    setup do
      Net::HTTP::proxy_object = self
    end

    should_eventually "deny access to people who disagree with me" do
    end
  end
end

All #should statements inside the context will proxy themselves through the integration test instead of hitting the network. Bam!

Check out the complete test file.

Look ma, no OSI layer 7!