Monday, November 29, 2010

Testing SOAP Webservices with RSpec

SOAP webservices are widely used in enterprise environments. Although they feel a bit clumsy in comparison to slim REST services, sometimes you have to deal with them.

The great thing is, to test such a service you are often free to use any tool you like. I like RSpec!

To query a web service you just need a few lines of code. I recommend Savon as SOAP client. It is used as shown here:

require 'rubygems'
require 'savon'

WSDL_URL  = 'http://www.webservicex.net/geoipservice.asmx?wsdl'

client = Savon::Client.new WSDL_URL
response = client.get_geo_ip do |soap|
  soap.body = { "wsdl:IPAddress" => "209.85.149.106" }
end
puts response

The response object can be converted to hash with the to_hash method, so you can fetch all values simply like you would do it with any other hash.

Now, the rest should be easy and is just a normal RSpec test:

require 'rubygems'
require 'savon'

WSDL_URL  = 'http://www.webservicex.net/geoipservice.asmx?wsdl'

RETURN_CODE_OK    = "1"
RETURN_CODE_ERROR = "0"

describe "Geo IP Webservice at #{WSDL_URL}" do
  
  # helper method
  def get_geo_ip_result ip
    response = @client.get_geo_ip do |soap|
      soap.body = {"wsdl:IPAddress" => ip}
    end
    response.to_hash[:get_geo_ip_response][:get_geo_ip_result]
  end
  
  before :all do
    @client = Savon::Client.new WSDL_URL
  end

  it "should yield a country name" do
    result = get_geo_ip_result "209.85.149.106"
    result[:country_name].should_not be_nil
    result[:return_code].should eql(RETURN_CODE_OK)
  end
 
  it "should return error for malformed ip address" do
    result = get_geo_ip_result "not.an.ip.address"
    result[:return_code].should eql(RETURN_CODE_ERROR)
  end
 
  it "should fail if no ip address is submitted" do
    lambda { @client.get_geo_ip }.should raise_error
  end

  # ...

end
Happy testing!

EDIT:

@dbloete pointed me to the fact that with RSpec 2 you can expect errors even more readable:
it "should fail if no ip address is submitted" do
  expect { @client.get_geo_ip }.to raise_error
end

5 comments:

  1. if you're using rspec and mocha, you could also try savon_spec (http://rubygems.org/gems/savon_spec) for non-integration tests based on soap response fixtures.

    ReplyDelete
  2. Thanks for the hint. I'll try it!

    ReplyDelete
  3. Hmmm, not sure about your specs. You are calling out the the web service each time you run the tests, there is no isolation here. Your tests will fail when the web service is down, even if your code is 100% correct. I think you should try to isolate your tests and only test the behavior.

    ReplyDelete
  4. Hey Anonymous,

    These specs should fail if the service is down. That's exactly the point.

    There are mainly three use cases to write tests like this:
    - to be sure that a foreign service behaves like assumed/documented. ( And don't change over time )
    - to have the safety that a service you provide works thru the hole stack.
    - to reproduce/document/fix bugs.

    Of course, I don't suggest to test the complete business logic thru a web service.

    I hope I could clarify my motivation.

    Cheers,
    Arbo

    ReplyDelete
  5. Umm no. Think for a minute. Are you testing the web service? or are you testing your methods which makes SOAP requests to the said service? Your test case is depending on way too many factors. If you were using it for a method that was processing the response and returning some result, then if it fails you will need to check both whether your code had some problem or whether the service was down.

    ReplyDelete