2008/04/13

Testing file uploads with RSpec on Rails

I was writing specs for a controller that processes uploaded images, and came up with this way of mocking a file upload control. So, I thought I'd share it.
Say you have a WibbleController which can replace the image belonging to a particular wibble. In your wibble/edit view you probably have something like this;


<% form_for(@wibble, :html => { :multipart => true }) do |f| %>
...
Replace with new image:
<%= f.file_field :new_file %>
...
<% end -%>

In your controller, there will be something that reads the streamed file data that the browser posts when you choose and submit a file.
In your spec, you can create an ActionController::UploadedStringIO object (which is what your controller will see), but it won't have access to the file data. So, we need to monkey patch it a bit.
The two methods we need to override are "read", which will return the file contents, and "size" (guess what that does). So, define a method in your spec file like so;

def mock_uploader(file, type = 'image/png')
filename = "%s/%s" % [ File.dirname(__FILE__), file ]
uploader = ActionController::UploadedStringIO.new
uploader.original_path = filename
uploader.content_type = type
def uploader.read
File.read(original_path)
end
def uploader.size
File.stat(original_path).size
end
uploader
end

Then, you could write a spec like this ('foo.png' should be a suitable image file in your spec/controllers directory);

it "should upload an image" do
Image.delete_all
uploader = mock_uploader 'foo.png'
post :update, {
:id => @object.id,
:wibble => { :image_file => uploader }
}
response.should be_success
Image.count.should == 1
i = Image.find(:first)
i.filename.should == uploader.original_path
i.contents.length.should == uploader.size
end

There may well be a better way to do this, in which case please let me know via the comments.