A few days back, I posted on my journey of understanding into the world of Ruby-based XML clients. This post is a continuation of that account.
Re-Arranging the WebEx Class
I figured it wouldn’t be long before I was back at the drawing board on my main architecture, and I was right. The earlier one I described turned out to be over-abstracted and hard to test.
It all started with my feeling that this looks really elegant:
1 | events = WebEx.request(Event.list) |
But in practice it turns out to be a little strange. “Attendee” and “Event” were two classes with no attributes of their own, and none of my code ever instantiated objects of these classes. These are two pretty obvious signs over an over-abstracted implementation. I’d been thinking that I’d write logic later which would (for example) instantiate objects of the Event class inside Event.list’s processor, but as I got more and more into the implementation, it just didn’t seem like I was going to need to mess with WebEx’s return values as discreetly defined objects. After all, I already had Events and Attendees represented as hashes in an array, which was working just fine for this first use case and the ones I could see on the horizon. Having separate classes for Event and Attendee would give me maximum extensibility, but at the cost of having pieces of overlong, over-organized code with no (current) purpose.
So I moved the Event class’s code into the WebEx class. Same with Attendee — now the WebEx class’s code looks like this:
As you can see, everything is now an instance method of the WebEx object. This means that the syntax for getting a list of Events is now:
1 2 | w = WebEx.new events = w.request(w.event_list) |
This still looks a little weird to me. I had been thinking that I should make WebEx#request into a class method, so as to have:
1 | events = WebEx.request(w.event_list) |
But that would mean having WebEx.request instantiate and return a new object of the WebEx class. There’s nothing wrong with that, but given the fact that another WebEx object already needs to be created in order to call one of its instance methods (event_list), it felt like a case where two objects of the same class which weren’t being used at all in the same way. Because of that awkwardness, I decided to live with the clunky-but-servicable all-instance-method approach. After all – there’s a good chance that I’ll refactor it yet again as I go… :-p
Testing
I’m embarrassed to not have spotted this earlier: the class as it had been written before was very hard to test for a couple major reasons:
- There was no way to override the XML attribute of one of the WebExmlObjects being returned by class methods Event.list and Attendee.list_for_meeting
- The HTTP request happened within the WebEx.request method, making it difficult to stub the HTTP request’s response, which had to happen in order to ensure that calling that method during testing didn’t involve net calls.
I solved each of these easily enough: I abstracted the HTTP request into its own method and I added a “payload” argument to each method that returned a WebExmlObject so that I could override its request XML.
After that, it was time to set up some fixtures. I created directories for “request” and “response” in my fixtures dir and added files containing the well-formed XML samples I got from the WebEx docs. Then I wrote methods for opening/reading each of them in my WebExSpecHelper module (this testing is all in RSpec). Below is a test that ensures that WebEx#request is calling WebEx#request_post:
1 2 3 4 | it "should call request_post" do @w.should_receive(:request_post).and_return(lst_summary_event_response) @w.request(@w.event_list) end |
@w is the instance variable that is created before every spec, and lst_summary_event_response is the name of the spec helper method that returns the fixture of that XML response. There’s no particular reason I called this one as opposed to an attendee-related method – I just needed to assert that the call would happen and then stipulate the response it would give, so any of my helper methods would do.
Here’s that helper method doing what it’s meant to:
1 2 3 4 | it "it should return all events if passed a nil time limit" do @events = @w.event_list(payload=lst_summary_event_request, time_limit=nil).processor.call(@w.doc) @events.length.should be(3) end |
There are three events in the fixture, so the length should be three when nothing is passed to time limit. All the fixture data from WebEx was in the past, so I altered the dates in there to have one event in the past, one in the future, and one in the future at a more distant date. Here’s what happens when you pass a time limit past that first (earlier) future date
1 2 3 4 5 6 | it "should return only events happening after the time limit" do middle_future_date = "04/02/2012 01:06:49" limit = @w.time_from_string(middle_future_date) @events = @w.event_list(payload=lst_summary_event_request, time_limit=limit).processor.call(@w.doc) @events.length.should be(1) end |
Only one result gets returned, because the fixture only has one event listing which has a start date after the date given.
Next Steps
So far, my unit tests have covered very little – basically just the processor portion of a WebExmlObject. For full coverage, I’ll need to test the
ml attribute which means validating the generated XML against the XML Schema Definitions(XSDs) WebEx provides with their API docs. Ruby doesn’t provide have any all-native tools for doing validation of XML against a given XSD, but the libxml library (which is distributed as a gem and gets its power from C-bindings it compiles at install time) will let you pass in a schema as a string and then validate against it.







Copyright © 2010 Catapult Creative - info(at)catapult(hyphen)creative(dot)com - Powered by