Wednesday, May 16, 2012

Unit testing Blather applications using RSpec


Blather is an easy to use XMPP client library written in Ruby. It seems to be actively developed and supported, as opposed to its competition XMPP4R library. I am using it in an application that has thousands of users interacting with it in real time.

Refer to https://github.com/sprsquish/blather for more details about the library.

This post demonstrates one way of writing concise tests for Blather based applications using RSpec.

There are different ways of writing Blather applications as shown in http://rubydoc.info/github/sprsquish/blather/master/Blather/DSL.

I have chosen the following approach of subclassing the Blather client, as it led to readable clients and simple tests.

#!/usr/bin/env ruby

require "blather/client/client"

class OurClient < Blather::Client
  def initialize
    super

    register_handler :subscription, :request? do |stanza|
      on_subscription(stanza)
    end

    register_handler :message, :chat?, :body do |stanza|
      on_message(stanza)
    end
  end

  def on_subscription(stanza)
    write stanza.approve!   # Approve the subscription request.
    write stanza.request!   # Request subscription in turn.
  end

  def on_message(stanza)
    # Echo the received message back to the sender.
    write Blather::Stanza::Message.new stanza.from, "You sent: #{stanza.body}"
  end
end


if __FILE__ == $0
  trap(:INT) { EM.stop }
  trap(:TERM) { EM.stop }

  client = OurClient.setup "@", "", "", 5222
  EM.run { client.run }
end

The application above demonstrates handling of subscription requests (invitations to add as a friend) and incoming messages. These are the two major interactions that a typical XMPP client engages in.

On receiving an invitation, our client simply accepts the invitation and sends a similar request to the sender. At the end of it, both the invitee and our client are subscribed to each other.

On receiving a message, our client simply echoes the message back, as an acknowledgement of the message.

Writing specs for this client is as simple:

require "spec_helper"
require "our_client"

describe OurClient do
  let(:client) { OurClient.new }
  before { client.stub!(:write) }

  describe "#initialize" do
    it "registers a handler for subscriptions." do
      stanza = Blather::Stanza::Presence::Subscription.new(random_email,
                                                           :subscribe)

      client.should_receive(:on_subscription).with(stanza)
      client.send :call_handler_for, :subscription, stanza
    end

    it "registers a handler for messages." do
      stanza = Blather::Stanza::Message.new(random_email, random_string)

      client.should_receive(:on_message).with(stanza)
      client.send :call_handler_for, :message, stanza
    end
  end

  describe "#on_subscription" do
    it "approves the subscription request." do
      stanza = Blather::Stanza::Presence::Subscription.new(random_email,
                                                           :subscribe)

      client.should_receive(:write).with(stanza.approve!)
      client.on_subscription(stanza)
    end

    it "sends a subscription request." do
      stanza = Blather::Stanza::Presence::Subscription.new(random_email,
                                                           :subscribe)

      client.should_receive(:write).with(stanza.request!)
      client.on_subscription(stanza)
    end
  end

  describe "#on_message" do
    it "echoes the response back." do
      body = random_string
      sender = random_email
      stanza = Blather::Stanza::Message.new(random_email, body)
      stanza.stub!(:from).and_return(sender)

      client.should_receive(:write) do |message|
        message.to.should == sender
        message.body.should == "You sent: #{body}"
      end
      client.on_message(stanza)
    end
  end
end

Sharing Your Mac OS X Desktops Using Google Drive

Here is what my Macbook Desktop looks like:


My iMac desktop in my office also looks exactly like the above. In fact, when I create a file on my Macbook desktop at home, it magically appears on my iMac desktop in the office.

My desktop now lives in the Cloud, thanks to Google Drive. There is no "special folder" that I have to remember putting my files into for them to be synced across all my machines.

How did I do this? Read on!

First, you have to install Google Drive. Go to http://drive.google.com and log into your Google account. Click on the "Get started with 5 GB free" button on the top.
Click on "Download Google Drive" link at the bottom of the left menu and follow instructions to download and install the DMG for Google Drive.

Launch Google Drive from your Application Menu (or Launchpad on OS X Lion) and sign in using your Google account.

Next, launch a terminal and change your Desktop to use Google Drive instead as follows:

$ cd
$ mv Desktop/* Google\ Drive/
$ rm -r Desktop
$ ln -s Google\ Drive/ Desktop

Log out and log back in.

Now your Desktop should start syncing and showing all your files in Google Docs. Any new files or folders that you create in your Desktop from now on will be synced across all machines that have Google Drive set up.

Repeat the aforementioned instructions for all machines which you want to keep in sync and enjoy the ride!