What a saga. I spent a chunk of this weekend working on a test for a SOAP web service.
(NOTE: For those of you suggesting RESTful is the way and the light, that's great, but in this case exposing a SOAP web service was an explicit requirement. So SOAP it is.)
Here's the setup:
- A friend wrote the actual SOAP web service using the datanoise ActionWebService gem.
- We're using rspec and cucumber for testing
- I was working on the tests right in the same project
Here's how I eventually got it working:
I assume you have at least one other cucumber test, so I'm not going to tell you how to set that up.
Install the following gems:
- datanoise-actionwebservice (2.3.2)
- httpclient (220.127.116.11)
- soap4r (1.5.8).
Also install any other gems your app needs.
Create cucumber files
Create a feature file of the form
.feature. You can find the API name by looking in app/services/. My service is app/services/foo_bar_api.rb, so my feature is foo_bar.feature.
Create a steps file of the form
_steps.rb. My steps file is named foo_bar_steps.rb.
Create base cucumber test
I freely admit this part isn't quite baked yet. My feature so far looks like this:
Web Services clients will need to talk to us from time to time. They should be able to communicate via SOAP
As an anonymous thing
I want to activate
So that I can be active
Given an "anonymous" thing
When I activate
Then I should receive an activation key
As you can see, I only have one scenario. My steps file looks like this:
Given /^an "([^\"]*)" appliance$/ do |type|
puts "okay we got it"
When /^I activate$/ do
wsdl = "http://localhost:3000/Foo_bar/wsdl"
user = 'catherine'
password = 'password'
driver = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver
driver.options["protocol.http.basic_auth"] << [wsdl, user, password]
#driver.wiredump_dev = STDOUT
result = driver.activation("desc", "12345")
Then /^I should receive an activation key$/ do
puts "say hooray"
Also, obviously, not fully implemented. Stay with me, though. The important bits are at the top and in the "When" area. Let's talk about what I'm doing here:
- I'm explicitly using the gem version of soap4r (the line "gem 'soap4r'"). That way I don't wind up using the version that shipped with Ruby.
- When I define my wsdl, I'm using the format "http:/myserver/myapiname
/wsdl". That seems to be the default place that ActionWebService puts it.
- Note that I'm not actually using ActionWebService (we only installed it because the server needs it); I'm only using soap4r.
- This web service is requires basic authentication. The "driver.options" line takes care of that.
- I'm using the dynamically generated method definitions (that soap4r gets from reading the wsdl). Thus "activation" is actually the name of the exposed SOAP method I'm calling. If, for example, my SOAP server exposed a method "horse", I would call "driver.horse(params)".
Okay, moving on.
Configure the environment
Add these two lines to your environment.rb:
This is so that the gem version of soap4r will load instead of the version that ships with Ruby.
Prep the Server
Before running the tests, you have to make sure that you have a user that can authenticate. Load this with factories, fixtures, mocks, a database insert statement, whatever, just as long as it's the username and password you specified in your steps file.
Then start your server. I've just been doing "script/server" in another terminal. There's probably a better way, but I haven't solved that problem yet.
Run Your Test
You're finally ready to run it. Use your preferred method. I used:
rake features FEATURE=features/cloudswitch_home.feature
And it should run (assuming your web service actually works!).
Lastly, here are a few of the mistakes I made along the way:
- Case matters, sort of. When you specify the wsdl, it doesn't have to match the case in your controller. However, httpclient will eventually turn this into an all-lowercase URL. If your URI is not all lowercase at that point, you'll get a 500 (internal server error) and it won't pass in your authentication information. (I had an ill-placed hard coding. And yes, I know I shouldn't have had it.)
- Ruby comes with a version of soap4r. Install the gem anyway and make sure you use that. The Ruby version of soap4r threw all kinds of errors for me.
- When you call the parameters, pass them in as direct arguments rather than as a hash. (i.e., call "driver.activation("desc", "12345")" rather than "driver.activation(:desc => "desc", :code => "12345")"). If you do the latter, it starts throwing errors like this: "wrong number of arguments (1 for 2) (ArgumentError)"
- If you see "uninitialized constant XSD::NS::KNOWN_TAG (NameError)" , it means you are using the Ruby soap4r instead of the gem. Put "gem 'soap4r'" in your environment.rb.
This is all still very rough, and I'll refine it as I get an actual test going. I did want to share, though. Thanks to the many blogs/API documentation/samples I found in Google for the assistance.