How do I stub out the flickraw library in my app's unit tests?
My Rails 2 app displays a slideshow of photos from Flickr via the flickraw library. My code works, but I'm stuck on how to properly write RSpec unit tests.
I have a Slide
class that encapsulates everything that my app needs from flickraw. It acts somewhat like a model object but doesn't use ActiveRecord. It doesn't do much work; it delegates most of the heavy lifting to flickraw.
I haven't completed the tests because, as it is now, they require me to hard-code in some photo IDs from Flickr, and the tests would break if I rearranged my photoset or added new photos.
So my so-called unit tests are more l开发者_如何学Cike integration tests. I understand how to write a mock or stub using RSpec, but not sure how to do it to the flickraw library. How do I stub out flickraw and turn this into a unit test?
slide.rb:
require 'flickraw'
FlickRaw.api_key = "xxx"
FlickRaw.shared_secret = "yyy"
flickr.auth.checkToken :auth_token => "zzz"
PHOTOSET_ID = 123123123
class Slide
attr_accessor :id, :previous_id, :next_id, :url_square, :url_thumbnail, :url_small, :url_medium500,
:url_medium640, :url_large, :url_original
def self.last
photoset = flickr.photosets.getPhotos(:photoset_id => PHOTOSET_ID)
Slide.new(photoset.photo.last.id)
end
def self.first
photoset = flickr.photosets.getPhotos(:photoset_id => PHOTOSET_ID)
Slide.new(photoset.photo.first.id)
end
def self.find(id)
Slide.new(id)
end
def initialize(id)
self.id = id
photo = flickr.photos.getInfo(:photo_id => id)
context = flickr.photosets.getContext(:photoset_id => PHOTOSET_ID, :photo_id => id)
sizes = flickr.photos.getSizes(:photo_id => id)
self.previous_id = (context.prevphoto.id == 0) ? nil : context.prevphoto.id
self.next_id = (context.nextphoto.id == 0) ? nil : context.nextphoto.id
sizes.each do |size|
if size.label == "Square"
self.url_square = size.source
elsif size.label == "Thumbnail"
self.url_thumbnail = size.source
elsif size.label == "Small"
self.url_small = size.source
elsif size.label == "Medium"
self.url_medium500 = size.source
elsif size.label == "Medium 640"
self.url_medium640 = size.source
elsif size.label == "Large"
self.url_large = size.source
elsif size.label == "Original"
self.url_original = size.source
end
end
end
end
slide_spec.rb:
require 'spec_helper'
describe Slide do
before(:each) do
first_photo_id = "444555666"
@slide = Slide.new(first_photo_id)
end
describe "urls" do
it "should generate the thumbnail url" do
@slide.url_thumbnail.should match(/_t.jpg$/)
end
it "should generate the small url" do
@slide.url_small.should match(/_m.jpg$/)
end
it "should generate the medium500 url" do
@slide.url_medium500.should match(/.jpg$/)
end
it "should generate the medium640 url" do
@slide.url_medium640.should match(/_z.jpg$/)
end
it "should generate the large url" do
@slide.url_large.should match(/_b.jpg$/)
end
it "should generate the original url" do
@slide.url_original.should match(/_o.jpg$/)
end
end
describe "finding" do
it "should find the correct last photo" do
# ???
end
it "should find the correct first photo" do
# ???
end
end
describe "context" do
it "should return the correct previous photo" do
# ???
end
it "should return the correct next photo" do
# ???
end
end
end
As I understand it, you should be able to do slide.stub!(:flickr).and_return to mock out anything that isn't inside the constructor. I query the fact that the constructor is loading so much from the flickr api though.
Can you change the implementation of slide so that instead of a load of attr_accessors, you have actual methods that get stuff from the flickr api? You should be able to do this without changing the external api of the class, and you can enable speed via caching the results of api calls in instance variables.
If you still want to have all that work done in the constructor, I'd recommend having a default argument that represents the flickr service. Your constructor then becomes the following:
def initialize(id, flickr=flickr)
… complicated setup code here…
end
This way, when you're testing, just pass in a mock flickr object like so:
flickr = mock(:flickr)
@slide = Slide.new(image_id, flickr)
You can then write the usual rspec assertions against the new flickr object (again, without changing the external api).
精彩评论