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

No comments: