Testing file uploads


#1

Hi all,

I was wondering about the class used when rails receives file uploads.
Sometimes it appears to be a File, and other times it is a StringIO.
Also it has methods such as original_filename that don’t appear to
belong to either of these classes. I ask because I’m trying to work out
how to test my fil upload related models and controllers.

Is there some standardised way of creating a file upload mock object?

Many thanks in advance.

CHEERS> SAM


#2

Hi Marcel,

Thanks - this is very helpful.

I also wonder is this the way that rails itself handles incoming files,
i.e. it takes a StringIO or File object and uses the extend operation to
add meta-data?

CHEERS> SAM


#3

On Fri, Jan 06, 2006 at 04:20:00AM +0900, Sam J. wrote:

I was wondering about the class used when rails receives file uploads.
Sometimes it appears to be a File, and other times it is a StringIO.
Also it has methods such as original_filename that don’t appear to
belong to either of these classes. I ask because I’m trying to work out
how to test my fil upload related models and controllers.

Is there some standardised way of creating a file upload mock object?

Many thanks in advance.

Here’s some code for testing file uploads. This has some bits that are
specific
to the implementation so you can’t just copy and paste this wholesale
and
expect it to work but perhaps it can provide some direction.

In the test_helper.rb:

module UploadedFile
attr_accessor :original_filename, :content_type
end

def uploaded_file(contents, name, content_type)
io = StringIO.new(contents)
io.extend UploadedFile
io.original_filename = name
io.content_type = content_type
io
end

def empty_trash!
FileUtils.rm_rf(Dir["#{Upload.upload_root}/*"])
end

def file_uploaded?(file_name)
File.exists?(uploaded_file_path(file_name))
end

def assert_file_uploaded(file_name)
assert file_uploaded?(file_name)
end

def assert_file_not_uploaded(file_name)
assert !file_uploaded?(file_name)
end

def assert_upload_equals(contents, file_name)
assert_file_uploaded file_name
assert_equal contents, IO.read(uploaded_file_path(file_name))
end

def uploaded_file_path(file_name)
namespace =
Upload.namespace_directories(@controller.send(:account).id)
File.join(Upload.upload_root, namespace, file_name)
end

The functional tests look something like this:

def teardown
empty_trash!
end

def test_basic_upload
assert_difference_in_size(Upload) do
upload = uploaded_file(‘Rock out!’, ‘nsync.mp3’, ‘audio/mpg’)
post :new, :upload => upload
assert_file_uploaded ‘nsync.mp3’
assert_not_nil assigns(:upload).upload_message
end
end

def test_all_upload_metadata_set
upload = uploaded_file(‘La la la’, ‘jason-in-shower.mp3’,
‘audio/mpg’)
post :new, :upload => upload
record = assigns(:upload)
assert_equal ‘La la la’.size, record.byte_size
assert_equal ‘audio/mpg’, record.content_type
assert_equal ‘jason-in-shower.mp3’, record.name
end

And finally, though not central to testing the uploading specifically,
here is
assert_difference_in_size (which I pretty much use all the time
everywhere):

def assert_difference_in_size(object, methods = nil, difference = 1)
methods ||= :count
apply = Proc.new {|obj, methods|
[*methods].inject(object) {|result, method| result.send(method)}
}
initial_value = apply.call(object, methods)
yield
assert_equal initial_value + difference, apply.call(object, methods)
end

def assert_no_difference_in_size(object, methods = nil, &block)
assert_difference_in_size object, methods, 0, &block
end

Quack!
marcel


#4

On Fri, Jan 06, 2006 at 04:20:00AM +0900, Sam J. wrote:

Many thanks in advance.

CHEERS> SAM

I do some basic file upload testing with this set up:

http://rubyi.st/show/9

YMMV, of course, depending on the complexity you need.

-Scott


#5

On Sun, Jan 08, 2006 at 08:18:38AM +0900, Sam J. wrote:

I also wonder is this the way that rails itself handles incoming files,
i.e. it takes a StringIO or File object and uses the extend operation to
add meta-data?

Rails isn’t responsible for any of this actually. It is ruby’s cgi.rb
that
handles file uploads and creating an IO object with metadata.

marcel