Testing File UploadProblemContributed 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. SolutionYou 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 DiscussionRails 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 |