Recipe 7.21. Testing File Upload


Problem

Contributed by: Evan Henshaw-Plath (rabble)

Your have an application that processes files submitted by users. You want a way to test the file-uploading functionality of your application as well as its ability to process the contents of the uploaded files.

Solution

You have a controller that accepts files as the :image param and writes them to the ./public/images/ directory from where they can later be served. A display message is set accordingly, whether or not saving the @image object is successful. (If the save fails, @image.errors will have a special error object with information about exactly why it failed to save.)

app/controllers/image_controller.rb:

def upload   @image = Image.new(params[:image])   if @image.save     notice[:message] = "Image Uploaded Successfully"   else      notice[:message] = "Image Upload Failed"   end end

Your Image model schema is defined by:

ActiveRecord::Schema.define() do   create_table "images", :force => true do |t|     t.column "title", :string, :limit => 80     t.column "path", :string     t.column "file_size", :integer     t.column "mime_type", :string     t.column "created_at", :datetime   end end

The Image model has an attribute for image_file but is added manually and will not be written in to the database. The model stores only the path to the file, not its contents. It writes the File object to a actual file in the ./public/images/ directory and it extracts information about the file, such as size and content type.

app/model/image_model.rb:

class Image < ActiveRecord::Base   attr_accessor :image_file   validates_presence_of :title, :path   before_create :write_file_to_disk   before_validation :set_path   def set_path     self.path = "#{RAILS_ROOT}/public/images/#{self.title}"   end   def write_file_to_disk     File.open(self.path, 'w') do |f|       f.write image_file.read     end   end end

To test uploads, construct a post where you pass in a mock file object, similar to what the Rails libraries do internally when a file is received as part of a post:

test/functional/image_controller_test.rb:

require File.dirname(__FILE__) + '/../test_helper' require 'image_controller'        # Re-raise errors caught by the controller. class ImageController; def rescue_action(e) raise e end; end           class ImageControllerTest < Test::Unit::TestCase   def setup     @controller = ImageController.new     @request    = ActionController::TestRequest.new     @response   = ActionController::TestResponse.new   end               def test_file_upload     post :upload, {       :image => {          :image_file => uploadable_file('test/mocks/image.jpg',                                         'image/jpeg'),          :title => 'My Test Image'       }     }          assert_kind_of? Image, assigns(:image),       'Did @image get created with a type of Image'     assert_equal 'My Test Image', assigns(:image).title,       'Did the image title get set?'   end end

You must create a mock file object that simulates all the methods of a file object when it's uploaded via HTTP. Note that the test expects a file called image.jpg to exist in your application's test/mocks/ directory.

Next, create the following helper method that will be available to all your tests:

test/test_helper.rb:

ENV["RAILS_ENV"] = "test" require File.expand_path(File.dirname(__FILE__) + "/../config/environment") require 'test_help' class Test::Unit::TestCase   self.use_transactional_fixtures = true   def uploadable_file( relative_path,                         content_type="application/octet-stream",                         filename=nil)     file_object = File.open("#{RAILS_ROOT}/#{relative_path}", 'r')     (class << file_object; self; end;).class_eval do        attr_accessor :original_filename, :content_type     end     file_object.original_filename ||=        File.basename("#{RAILS_ROOT}/#{relative_path}")     file_object.content_type = content_type     return file_object   end end

Discussion

Rails adds special methods to the file objects that are created via an HTTP POST. To properly test file uploads you need to open a file object and add those methods. Once you upload a file, by default, Rails places it in the /tmp/ directory. Your controller and model code will need to take the file object and write it to the filesystem or the database.

File uploads in Rails are passed in simply as one of the parameters in the params hash. Rails reads in the HTTP POST and CGI parameters and automatically creates a file object. It is up your controller to handle that file object and write it to a file on disk, place it in the database, or process and discard it.

The convention is that you store files for tests in the ./test/mocks/test/ directory. It's important that you have routines that clean up any files that are saved locally by your tests. You should add a teardown method to your functional tests that performs this task.

The following example shows how you can add a custom clean-up method, which deletes any image files you may have previously uploaded. teardown, like setup, is called for each test method in the class. We know from the above that all images are getting written to the ./public/images/ directory, so we just need to delete everything from that directory after each test. teardown is run regardless of whether the test passes or fails.

test/functional/image_controller_test.rb:

def teardown   FileUtils.rm_r "#{RAILS_ROOT}/public/backup_images/", :force => true   FileUtils.mkdir "#{RAILS_ROOT}/public/backup_images/" end

See Also

  • Section 14.8"

  • Section 15.2"




Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net