Saturday, November 13, 2010

Testing Heroku's SMS addon with Cucumber

(UPDATE: All code on this page is also available as a gist here: GIST)

I love Heroku's add-ons.

I love Cucumber testing.

But I sometimes hate putting the two together.  How do you test the infrastructure you don't know much about?  I've been confronted with this recently with the "moonshado-sms" addon, which is a super-simple way to hook your application up to a cheap sms gateway.  My integration tests were difficult, though.  How do I check that everything got sent appropriately?  That the SMS went to the right person, with the right number, and the right message?

I stumbled a bit, but came up with something that I think is pretty clever, and I'm going to share it here in case you have the same insatiable desire to see a series of green dots before deploying that I do.  In my env.rb file (for cucumber), at the bottom, I include some monkey patching of Moonshado's API:



module Moonshado
  class Sms
    cattr_accessor :sent_messages
   
    def deliver_sms
      raise MoonshadoSMSException.new("Invalid message") unless is_message_valid?(@message)

      data = {:sms => {:device_address => format_number(@number), :message => @message.to_s}}

      self.class.sent_messages ||= []
      self.class.sent_messages << data
      response = RestClient::Response.create('{"stat":"ok","id":"sms_id_mock"}', "", {})

      parse(response.to_s)
    rescue MoonshadoSMSException => exception
      raise exception
    end
  end
end

Now mix in some clever step defenitions with "sms_steps.rb" in your cucumber "step_definitions" folder:

When /^the sms message "([^"]*)" is sent to "([^"]*)"$/ do |number, message|
  Moonshado::Sms.new(message,number).deliver_sms
end

Then /^there should be an SMS sent to "([^"]*)" saying "([^"]*)"$/ do |number, sms_text|
  messages = Moonshado::Sms.sent_messages
  messages = Moonshado::Sms.sent_messages.select{|data| 
    data[:sms][:device_address] == number and data[:sms][:message] == sms_text
  }
  messages.size.should == 1
  messages.each{|msg| Moonshado::Sms.sent_messages.delete(msg)}
end

Then /^there should be no SMS messages sent$/ do 
  if Moonshado::Sms.sent_messages
    Moonshado::Sms.sent_messages.size.should == 0
  end
end

Take and edit for your own purposes, and may your code always be well tested.

(PS: All code on this page is also available as a gist here: GIST)